hive-forge: make --json a global flag (closes #421)

This commit is contained in:
damocles 2026-05-26 14:36:00 +02:00 committed by Mara
commit d0a1ee037b
5 changed files with 33 additions and 16 deletions

View file

@ -124,7 +124,7 @@ since #280). Use it instead of ad-hoc curl pipelines:
```bash ```bash
hive-forge view 42 # title + body + comments hive-forge view 42 # title + body + comments
hive-forge comments 42 # list all comments (human-readable) hive-forge comments 42 # list all comments (human-readable)
hive-forge comments 42 --json # list as JSON array hive-forge --json comments 42 # same as above, JSON array (global flag, closes #421)
hive-forge comment 42 --body "..." # post comment (inline body) hive-forge comment 42 --body "..." # post comment (inline body)
hive-forge comment 42 --body-file - <<EOF # ...or pipe a HEREDOC hive-forge comment 42 --body-file - <<EOF # ...or pipe a HEREDOC
multi-line body multi-line body

View file

@ -26,14 +26,19 @@ pub struct Client {
/// Default repo used when a verb doesn't carry an explicit /// Default repo used when a verb doesn't carry an explicit
/// `[repo]` override. /// `[repo]` override.
pub default_repo: String, pub default_repo: String,
/// Global `--json` flag — verbs that have a human-readable
/// default path branch on `client.json_mode()` to pick the
/// JSON output shape instead. Closes #421.
json_mode: bool,
} }
impl Client { impl Client {
/// Build a client from the standard environment variables. /// Build a client from the standard environment variables.
/// `repo_override` (from the global `-r/--repo` flag) takes /// `repo_override` (from the global `-r/--repo` flag) takes
/// priority over `HIVE_FORGE_REPO`; the env var is the fallback /// priority over `HIVE_FORGE_REPO`; the env var is the fallback
/// default. /// default. `json_mode` comes from the global `--json` flag —
pub fn from_env(repo_override: Option<String>) -> Result<Self> { /// per-verb output formatters key off it via `Client::json_mode`.
pub fn from_env(repo_override: Option<String>, json_mode: bool) -> Result<Self> {
let base = std::env::var("HIVE_FORGE_URL").unwrap_or_else(|_| DEFAULT_URL.to_owned()); let base = std::env::var("HIVE_FORGE_URL").unwrap_or_else(|_| DEFAULT_URL.to_owned());
let default_repo = repo_override let default_repo = repo_override
.or_else(|| std::env::var("HIVE_FORGE_REPO").ok()) .or_else(|| std::env::var("HIVE_FORGE_REPO").ok())
@ -55,9 +60,19 @@ impl Client {
http, http,
base, base,
default_repo, default_repo,
json_mode,
}) })
} }
/// True when the operator passed the global `--json` flag.
/// Verbs that have a human-readable default branch on this to
/// emit JSON instead. Verbs whose only output format is JSON
/// (e.g. `issue`, `pr`) can ignore it.
#[must_use]
pub fn json_mode(&self) -> bool {
self.json_mode
}
/// Resolve the API base path (`<base>/api/v1`). /// Resolve the API base path (`<base>/api/v1`).
fn api(&self) -> String { fn api(&self) -> String {
format!("{}/api/v1", self.base) format!("{}/api/v1", self.base)

View file

@ -35,6 +35,12 @@ struct Cli {
/// positional the bash helper used. /// positional the bash helper used.
#[arg(short = 'r', long, global = true)] #[arg(short = 'r', long, global = true)]
repo: Option<String>, repo: Option<String>,
/// Emit JSON output instead of the verb's default human-readable
/// shape, for verbs that support both (closes #421). Verbs whose
/// only output is already JSON (`issue`, `pr`, etc.) ignore this
/// flag — they always print JSON regardless.
#[arg(long, global = true)]
json: bool,
#[command(subcommand)] #[command(subcommand)]
verb: Verb, verb: Verb,
} }
@ -87,7 +93,8 @@ enum Verb {
fn main() -> Result<()> { fn main() -> Result<()> {
let cli = Cli::parse(); let cli = Cli::parse();
let client = client::Client::from_env(cli.repo).context("initialize forge client")?; let client =
client::Client::from_env(cli.repo, cli.json).context("initialize forge client")?;
match cli.verb { match cli.verb {
Verb::View(a) => verbs::view::run(&client, a), Verb::View(a) => verbs::view::run(&client, a),
Verb::Issue(a) => verbs::issue::run(&client, a), Verb::Issue(a) => verbs::issue::run(&client, a),

View file

@ -1,5 +1,5 @@
//! `comment-show <id> [--json] [repo]` — print the body (or full //! `comment-show <id>` — print the body (or full JSON envelope
//! JSON) of a single comment by id. //! when `--json` is set globally) of a single comment by id.
use anyhow::Result; use anyhow::Result;
use clap::Args as ClapArgs; use clap::Args as ClapArgs;
@ -12,15 +12,12 @@ use crate::verbs::print_json;
pub struct Args { pub struct Args {
/// Comment id. /// Comment id.
id: u64, id: u64,
/// Print full JSON envelope instead of just the body text.
#[arg(long)]
json: bool,
} }
pub fn run(client: &Client, args: Args) -> Result<()> { pub fn run(client: &Client, args: Args) -> Result<()> {
let repo = client.repo(); let repo = client.repo();
let v = client.get_json(&format!("/repos/{repo}/issues/comments/{}", args.id))?; let v = client.get_json(&format!("/repos/{repo}/issues/comments/{}", args.id))?;
if args.json { if client.json_mode() {
let trimmed = json!({ let trimmed = json!({
"id": v.get("id"), "id": v.get("id"),
"user": v.get("user").and_then(|u| u.get("login")), "user": v.get("user").and_then(|u| u.get("login")),

View file

@ -1,5 +1,6 @@
//! `comments <number> [--json] [--limit N]` — list all comments on //! `comments <number> [--limit N]` — list all comments on an issue
//! an issue or PR. Closes the curl-fallback gap (#418). //! or PR. Closes the curl-fallback gap (#418). Use the global
//! `--json` flag for JSON output (#421).
use anyhow::Result; use anyhow::Result;
use clap::Args as ClapArgs; use clap::Args as ClapArgs;
@ -12,9 +13,6 @@ use crate::verbs::print_json;
pub struct Args { pub struct Args {
/// Issue or PR number. /// Issue or PR number.
number: u64, number: u64,
/// Print as JSON array instead of human-readable markdown.
#[arg(long)]
json: bool,
/// Page size (Forgejo caps at 50 by default). /// Page size (Forgejo caps at 50 by default).
#[arg(long, default_value_t = 50)] #[arg(long, default_value_t = 50)]
limit: u64, limit: u64,
@ -26,7 +24,7 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
"/repos/{repo}/issues/{}/comments?limit={}", "/repos/{repo}/issues/{}/comments?limit={}",
args.number, args.limit args.number, args.limit
))?; ))?;
if args.json { if client.json_mode() {
let trimmed: Vec<Value> = v let trimmed: Vec<Value> = v
.as_array() .as_array()
.map(|a| { .map(|a| {