Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/hive-forge/src/verbs/attachment_get.rs
iris 17554ea563 fix: sync generated hive-forge CLI docs with their clap source strings
Same bug as the swarmctl/hivectl fix, a third instance argus's review
didn't name but nix/checks.nix's hive-forge-docs freshness check (same
pattern as hivectl-docs/swarmctl-docs) caught in CI: the earlier
Contractions/Foreign/Auto batches edited docs/tools/forge-cli.md
directly instead of the clap #[arg(...)]/doc-comment strings in
hive-forge/src/main.rs and hive-forge/src/verbs/*.rs.

Applied the same 13 wording changes to source that the earlier commits
already made to the generated .md, matched 1:1 against
'git diff origin/main HEAD -- docs/tools/forge-cli.md' rather than
guessed. Several source doc comments feed two rendered sections each
(e.g. reaction.rs's one Add-variant doc renders under both
'issue reaction add' and 'pr reaction add', since both subcommands
share the same enum) -- one source fix, two generated-doc fixes.

Regenerated from the now-fixed source and confirmed byte-identical to
what's already committed (diff exit 0) -- source and generated output
are back in sync, same as the swarmctl/hivectl fix.

cargo clippy -p hive-forge --all-targets -- -D warnings and
scripts/check-doc-refs.sh both clean.
2026-09-07 16:28:06 +02:00

93 lines
3.1 KiB
Rust

//! `attachment-get <uuid-or-url> [-o <path>]` — download a Forgejo
//! attachment to a local file (or stdout with `-o -`). Accepts either
//! the bare attachment UUID or any URL form that ends with
//! `/attachments/<uuid>` (as it appears in comment markdown).
//!
//! Default output path: `/tmp/forge-attachment-<uuid>`. Prints the
//! resolved path to stdout so callers can capture it
//! (`path=$(hive-forge attachment-get <uuid>)`).
use std::io::Write as _;
use std::path::PathBuf;
use anyhow::{Result, bail};
use clap::Args as ClapArgs;
use crate::client::Client;
#[derive(ClapArgs)]
pub struct Args {
/// Attachment UUID or URL. Accepts a bare UUID (`abc-123-...`),
/// a root-relative path (`/attachments/abc-123-...`), or a full
/// URL (`http://localhost:3000/attachments/abc-123-...`).
attachment: String,
/// Output path. Defaults to `/tmp/forge-attachment-<uuid>`.
/// Pass `-` to write raw bytes to stdout (for example for piping to an
/// image viewer).
#[arg(short = 'o', long)]
output: Option<String>,
}
/// # Errors
///
/// Returns an error if the attachment UUID cannot be extracted from
/// the input, if the download fails (network, 4xx/5xx), or if the
/// output path cannot be written.
pub fn run(client: &Client, args: Args) -> Result<()> {
let uuid = extract_uuid(&args.attachment)?;
let url = client.attachment_url(&uuid);
let bytes = client.get_bytes_raw(&url)?;
match args.output.as_deref() {
Some("-") => {
std::io::stdout()
.write_all(&bytes)
.map_err(|e| anyhow::anyhow!("write stdout: {e}"))?;
}
Some(path) => {
let p = PathBuf::from(path);
std::fs::write(&p, &bytes)
.map_err(|e| anyhow::anyhow!("write {}: {e}", p.display()))?;
println!("{}", p.display());
}
None => {
let p = PathBuf::from(format!("/tmp/forge-attachment-{uuid}"));
std::fs::write(&p, &bytes)
.map_err(|e| anyhow::anyhow!("write {}: {e}", p.display()))?;
println!("{}", p.display());
}
}
Ok(())
}
/// Extract the bare UUID from whatever the caller passes.
///
/// Accepted forms:
/// - bare UUID: `abc-123-...`
/// - root-relative path: `/attachments/abc-123-...`
/// - full loopback URL: `http://localhost:3000/attachments/abc-123-...`
/// - any URL ending with `/attachments/<uuid>`
///
/// # Errors
///
/// Returns an error if the input contains no path segment after
/// parsing (e.g. an empty string).
fn extract_uuid(input: &str) -> Result<String> {
// If it contains a slash, treat everything after the last slash as
// the UUID. This handles all URL/path forms.
let uuid = if input.contains('/') {
input
.rsplit('/')
.next()
.filter(|s| !s.is_empty())
.ok_or_else(|| {
anyhow::anyhow!("hive-forge attachment-get: cannot extract UUID from {input:?}")
})?
} else {
input
};
if uuid.is_empty() {
bail!("hive-forge attachment-get: empty UUID in {input:?}");
}
Ok(uuid.to_owned())
}