hyperhive/hive-forge/src/verbs/reaction.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

113 lines
4.3 KiB
Rust

//! `reaction <number> [--comment <id>] [list|add|remove] [content]` —
//! list, add, or remove emoji reactions on an issue/PR or one of its
//! comments.
//!
//! Forgejo's reaction endpoints work on the shared issue/PR index (PRs
//! are issues internally, same as `dependency`) when no `--comment` is
//! given; `--comment <id>` redirects to the separate comment-reaction
//! endpoint pair, keyed on the comment's own id (not the parent
//! issue/PR number — `number` still has to be passed so this verb slots
//! into `issue <n> reaction` / `pr <n> reaction` like every other
//! generic verb, but it's otherwise unused on that path).
//!
//! `content` is Forgejo's reaction shortcode vocabulary (`+1`, `-1`,
//! `laugh`, `confused`, `heart`, `hooray`, `rocket`, `eyes`, …), not a
//! raw emoji character — same set the forge web UI's reaction picker
//! offers. `--list-allowed` prints the *actual* instance-configured
//! list (`GET /settings/ui`'s `allowed_reactions`) instead of leaving a
//! caller to guess it or learn it from a 403 — this is instance-global,
//! not issue/PR-scoped, so `number`/`--comment` are ignored when it's
//! set (the number is still required positionally, same as every other
//! generic verb, but unused on this path).
use anyhow::Result;
use clap::{Args as ClapArgs, Subcommand};
use forgejo_api::structs::EditReactionOption;
use crate::client::{Client, index};
use crate::verbs::{comment_reactions, issue_reactions, print_json};
#[derive(ClapArgs)]
pub struct Args {
/// Issue or PR number. Ignored (but still required) when
/// `--list-allowed` is set.
pub(crate) number: u64,
/// Target a specific comment's reactions instead of the issue/PR
/// itself — the comment's own id (from `comments`/`comment-show`),
/// not its position in the thread.
#[arg(long)]
comment: Option<u64>,
/// Print the instance's actual configured reaction shortcodes
/// (`GET /settings/ui`) instead of listing/adding/removing —
/// instance-global, ignores `number`/`--comment`.
#[arg(long)]
pub(crate) list_allowed: bool,
#[command(subcommand)]
action: Option<Action>,
}
#[derive(Subcommand)]
enum Action {
/// List reactions (default when no action is given).
List,
/// Add a reaction — a Forgejo shortcode, for example `+1`, `heart`, `rocket`.
Add { content: String },
/// Remove your own reaction with this content.
Remove { content: String },
}
pub fn run(client: &Client, args: Args) -> Result<()> {
if args.list_allowed {
let settings = client.api().get_general_ui_settings().send()?;
return print_json(&serde_json::json!(
settings.allowed_reactions.unwrap_or_default()
));
}
let (owner, name) = client.owner_repo()?;
match args.action.unwrap_or(Action::List) {
Action::List => {}
Action::Add { content } => {
let body = EditReactionOption {
content: Some(content),
};
match args.comment {
Some(id) => {
client
.api()
.issue_post_comment_reaction(owner, name, index(id)?, body)
.send()?;
}
None => {
client
.api()
.issue_post_issue_reaction(owner, name, index(args.number)?, body)
.send()?;
}
}
}
Action::Remove { content } => {
let body = EditReactionOption {
content: Some(content),
};
match args.comment {
Some(id) => {
client
.api()
.issue_delete_comment_reaction(owner, name, index(id)?, body)
.send()?;
}
None => {
client
.api()
.issue_delete_issue_reaction(owner, name, index(args.number)?, body)
.send()?;
}
}
}
}
let reactions = match args.comment {
Some(id) => comment_reactions(client, owner, name, id)?,
None => issue_reactions(client, owner, name, args.number)?,
};
print_json(&serde_json::json!(reactions))
}