hyperhive/hive-forge/src/body.rs

56 lines
2 KiB
Rust

//! Body-input resolution shared by every verb that posts a body.
//! Matches the bash `resolve_body` helper (#382): exactly one source
//! between `--body`, `--body-file`, and piped stdin. Passing both
//! `--body` and `--body-file` is a clear error.
use std::io::{IsTerminal, Read};
use anyhow::{Context, Result, bail};
/// Resolve the body to send, given the user's explicit flags and the
/// current stdin state. Returns `None` when none of the sources are
/// available (the caller decides whether that's allowed — e.g.
/// `issue-edit` treats absent body as "leave unchanged", while
/// `comment` treats absent body as a hard error).
pub fn resolve(body: Option<&str>, file: Option<&str>) -> Result<Option<String>> {
if body.is_some() && file.is_some() {
bail!("hive-forge: --body and --body-file are mutually exclusive");
}
if let Some(b) = body {
return Ok(Some(b.to_owned()));
}
if let Some(path) = file {
if path == "-" {
return Ok(Some(read_stdin().context("read stdin for --body-file -")?));
}
let s = std::fs::read_to_string(path)
.with_context(|| format!("read --body-file {path}"))?;
return Ok(Some(s));
}
if !std::io::stdin().is_terminal() {
return Ok(Some(read_stdin().context("read piped stdin")?));
}
Ok(None)
}
/// Resolve, then require a non-empty result (whitespace-trimmed).
/// Used by `comment` / `comment-edit` which refuse to post empty
/// bodies.
pub fn resolve_required(body: Option<&str>, file: Option<&str>, verb: &str) -> Result<String> {
let resolved = resolve(body, file)?;
let Some(s) = resolved else {
bail!(
"hive-forge {verb}: no body — pass --body <text>, --body-file <path>, or pipe via stdin"
);
};
if s.trim().is_empty() {
bail!("hive-forge {verb}: refusing to post empty body");
}
Ok(s)
}
fn read_stdin() -> std::io::Result<String> {
let mut s = String::new();
std::io::stdin().read_to_string(&mut s)?;
Ok(s)
}