hive-forge: rewrite bash CLI helper as a rust binary (closes #280)

This commit is contained in:
damocles 2026-05-25 01:30:44 +02:00 committed by Mara
commit 595e3c040c
28 changed files with 1434 additions and 612 deletions

View file

@ -0,0 +1,87 @@
//! `issue-edit <number> [--title <t>] [body sources] [--state s]
//! [--milestone id] [repo]` — partial update of an issue. Fields not
//! provided are left unchanged.
use anyhow::Result;
use clap::{Args as ClapArgs, ValueEnum};
use serde_json::{Map, Value, json};
use crate::body;
use crate::client::Client;
use crate::verbs::print_json;
#[derive(Copy, Clone, ValueEnum)]
pub enum StateArg {
Open,
Closed,
}
impl StateArg {
fn as_str(self) -> &'static str {
match self {
Self::Open => "open",
Self::Closed => "closed",
}
}
}
#[derive(ClapArgs)]
pub struct Args {
/// Issue number.
number: u64,
/// New title (omit to leave unchanged).
#[arg(long)]
title: Option<String>,
/// Inline body text (omit to leave unchanged).
#[arg(long, conflicts_with = "body_file")]
body: Option<String>,
/// Read body from a file. `-` means stdin.
#[arg(long = "body-file")]
body_file: Option<String>,
/// New state.
#[arg(long, value_enum)]
state: Option<StateArg>,
/// Milestone id (0 to unset).
#[arg(long)]
milestone: Option<u64>,
/// Repo override.
repo: Option<String>,
}
pub fn run(client: &Client, args: Args) -> Result<()> {
// Body is partial: only update the body field if a source was
// actually given. Piped stdin without --body/--body-file leaves
// body alone (the partial-update contract).
let body_explicit = args.body.is_some() || args.body_file.is_some();
let body = if body_explicit {
body::resolve(args.body.as_deref(), args.body_file.as_deref())?
} else {
None
};
let mut payload = Map::new();
if let Some(t) = args.title {
payload.insert("title".into(), Value::String(t));
}
if let Some(b) = body {
payload.insert("body".into(), Value::String(b));
}
if let Some(s) = args.state {
payload.insert("state".into(), Value::String(s.as_str().to_owned()));
}
if let Some(m) = args.milestone {
payload.insert("milestone".into(), Value::Number(m.into()));
}
let repo = client.repo(args.repo.as_deref());
let resp = client.patch_json(
&format!("/repos/{repo}/issues/{}", args.number),
&Value::Object(payload),
)?;
print_json(&json!({
"number": resp.get("number"),
"title": resp.get("title"),
"state": resp.get("state"),
"milestone": resp.get("milestone").and_then(|m| m.get("title")),
}))
}