@@ -40,6 +40,47 @@
#[arg(long)]
due_date: Option<String>,
},
/// Edit a milestone's title / description / due date
Edit {
/// Milestone short ID or UUID
id: String,
/// New title
#[arg(long)]
title: Option<String>,
/// New description
#[arg(long)]
description: Option<String>,
/// New due date (YYYY-MM-DD)
#[arg(long)]
due_date: Option<String>,
/// Repository (org/repo)
#[arg(long)]
repo: Option<String>,
},
/// Close a milestone
Close {
/// Milestone short ID or UUID
id: String,
/// Repository (org/repo)
#[arg(long)]
repo: Option<String>,
},
/// Reopen a closed milestone
Reopen {
/// Milestone short ID or UUID
id: String,
/// Repository (org/repo)
#[arg(long)]
repo: Option<String>,
},
/// Delete a milestone
Delete {
/// Milestone short ID or UUID
id: String,
/// Repository (org/repo)
#[arg(long)]
repo: Option<String>,
},
}
#[derive(Debug, Deserialize)]
@@ -69,9 +110,94 @@
description,
due_date,
} => create(repo.as_deref(), &title, &description, due_date.as_deref()).await,
MilestoneCommand::Edit {
id,
title,
description,
due_date,
repo,
} => {
edit(
repo.as_deref(),
&id,
title.as_deref(),
description.as_deref(),
due_date.as_deref(),
)
.await
}
MilestoneCommand::Close { id, repo } => set_state(repo.as_deref(), &id, "close").await,
MilestoneCommand::Reopen { id, repo } => set_state(repo.as_deref(), &id, "reopen").await,
MilestoneCommand::Delete { id, repo } => delete(repo.as_deref(), &id).await,
}
}
async fn edit(
repo: Option<&str>,
id: &str,
title: Option<&str>,
description: Option<&str>,
due_date: 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!({});
if let Some(v) = title {
payload["title"] = serde_json::json!(v);
}
if let Some(v) = description {
payload["description"] = serde_json::json!(v);
}
if let Some(v) = due_date {
payload["due_date"] = serde_json::json!(v);
}
let resp: serde_json::Value = client
.patch(&format!("/{org}/{name}/milestones/{id}"), &payload)
.await?;
if output::is_json() {
output::json_ok("milestone", resp["milestone"].clone());
return Ok(());
}
output::success(&format!("Updated milestone '{id}'"));
Ok(())
}
async fn set_state(
repo: Option<&str>,
id: &str,
verb: &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}/milestones/{id}/{verb}"),
&serde_json::json!({}),
)
.await?;
if output::is_json() {
output::json_ok("milestone", resp["milestone"].clone());
return Ok(());
}
let state = resp["milestone"]["state"].as_str().unwrap_or(verb);
output::success(&format!("Milestone '{id}' is now {state}"));
Ok(())
}
async fn delete(repo: Option<&str>, id: &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}/milestones/{id}"))
.await?;
if output::is_json() {
output::json_ok("deleted", serde_json::json!(id));
return Ok(());
}
output::success(&format!("Deleted milestone '{id}'"));
Ok(())
}
pub async fn create(
repo: Option<&str>,
title: &str,
@@ -105,6 +231,10 @@
.and_then(|v| v.as_str())
.unwrap_or("?");
if output::is_json() {
output::json_ok("milestone", resp["milestone"].clone());
return Ok(());
}
output::success(&format!("Created milestone {short_id}: {title}"));
Ok(())
}
@@ -114,6 +244,10 @@
let (org, name) = config::resolve_repo(repo)?;
let resp: serde_json::Value = client.get(&format!("/{org}/{name}/milestones")).await?;
if output::is_json() {
output::print_json(&resp);
return Ok(());
}
if output::is_json() {
output::print_json(&resp);
@@ -161,6 +295,10 @@
let resp: serde_json::Value = client
.get(&format!("/{org}/{name}/milestones/{id}"))
.await?;
if output::is_json() {
output::print_json(&resp);
return Ok(());
}
if output::is_json() {
output::print_json(&resp);