feat(hive-forge): repo-search verb — keyword/topic search across forge repos
This commit is contained in:
parent
3e2565d355
commit
327a3bc3bc
3 changed files with 91 additions and 0 deletions
|
|
@ -113,6 +113,10 @@ enum Verb {
|
||||||
/// the valid names + descriptions for triage / labelling. `--json`
|
/// the valid names + descriptions for triage / labelling. `--json`
|
||||||
/// emits the full label objects (id, name, color, description).
|
/// emits the full label objects (id, name, color, description).
|
||||||
RepoLabels(verbs::repo_labels::Args),
|
RepoLabels(verbs::repo_labels::Args),
|
||||||
|
/// Search for repositories on the forge instance by keyword, topic, or
|
||||||
|
/// description. Not repo-scoped — queries the instance-wide explore
|
||||||
|
/// endpoint. `--json` emits the full repository objects.
|
||||||
|
RepoSearch(verbs::repo_search::Args),
|
||||||
/// Triage lint queries (unassigned / no-reviewer / stale-branches / assignments).
|
/// Triage lint queries (unassigned / no-reviewer / stale-branches / assignments).
|
||||||
Lint(verbs::lint::Args),
|
Lint(verbs::lint::Args),
|
||||||
/// List issues / PRs with filters (`--kind`, `--state`, `--assignee`,
|
/// List issues / PRs with filters (`--kind`, `--state`, `--assignee`,
|
||||||
|
|
@ -198,6 +202,7 @@ fn main() -> Result<()> {
|
||||||
Verb::RepoCreate(a) => verbs::repo_create::run(&client, a),
|
Verb::RepoCreate(a) => verbs::repo_create::run(&client, a),
|
||||||
Verb::RepoAddCollaborator(a) => verbs::repo_add_collaborator::run(&client, a),
|
Verb::RepoAddCollaborator(a) => verbs::repo_add_collaborator::run(&client, a),
|
||||||
Verb::RepoLabels(a) => verbs::repo_labels::run(&client, a),
|
Verb::RepoLabels(a) => verbs::repo_labels::run(&client, a),
|
||||||
|
Verb::RepoSearch(a) => verbs::repo_search::run(&client, a),
|
||||||
Verb::Lint(a) => verbs::lint::run(&client, a),
|
Verb::Lint(a) => verbs::lint::run(&client, a),
|
||||||
Verb::List(a) => verbs::list::run(&client, a),
|
Verb::List(a) => verbs::list::run(&client, a),
|
||||||
Verb::Milestone(a) => verbs::milestone::run(&client, a),
|
Verb::Milestone(a) => verbs::milestone::run(&client, a),
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ pub mod reopen;
|
||||||
pub mod repo_add_collaborator;
|
pub mod repo_add_collaborator;
|
||||||
pub mod repo_create;
|
pub mod repo_create;
|
||||||
pub mod repo_labels;
|
pub mod repo_labels;
|
||||||
|
pub mod repo_search;
|
||||||
pub mod subscription;
|
pub mod subscription;
|
||||||
pub mod timeline;
|
pub mod timeline;
|
||||||
pub mod tree_sha;
|
pub mod tree_sha;
|
||||||
|
|
|
||||||
85
hive-forge/src/verbs/repo_search.rs
Normal file
85
hive-forge/src/verbs/repo_search.rs
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
//! `repo-search [--query <kw>] [--topic] [--include-desc] [--limit N]` —
|
||||||
|
//! search for repositories on the forge instance.
|
||||||
|
//!
|
||||||
|
//! Wraps `GET /repos/search` (Forgejo explore endpoint). Useful for agents
|
||||||
|
//! that need to find a repo by keyword rather than knowing the exact
|
||||||
|
//! `owner/repo` slug upfront. Not repo-scoped — the `-r` flag is ignored.
|
||||||
|
//!
|
||||||
|
//! Human output: one `owner/repo description` line per hit. `--json`
|
||||||
|
//! emits the full `SearchResults.data` array as raw JSON.
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Args as ClapArgs;
|
||||||
|
use forgejo_api::structs::RepoSearchQuery;
|
||||||
|
|
||||||
|
use crate::client::Client;
|
||||||
|
use crate::verbs::print_json;
|
||||||
|
|
||||||
|
#[derive(ClapArgs)]
|
||||||
|
pub struct Args {
|
||||||
|
/// Keyword to search for (matches repo name by default; combine with
|
||||||
|
/// `--include-desc` to also match description text).
|
||||||
|
#[arg(short = 'q', long)]
|
||||||
|
query: Option<String>,
|
||||||
|
/// Restrict matches to repositories that have the keyword as a **topic**
|
||||||
|
/// tag rather than in the name.
|
||||||
|
#[arg(long)]
|
||||||
|
topic: bool,
|
||||||
|
/// Extend the keyword search to repository descriptions (in addition to
|
||||||
|
/// names, or topics when `--topic` is set).
|
||||||
|
#[arg(long = "include-desc")]
|
||||||
|
include_desc: bool,
|
||||||
|
/// Maximum number of results to return (default: 30).
|
||||||
|
#[arg(long, default_value_t = 30, value_parser = clap::value_parser!(u64).range(1..))]
|
||||||
|
limit: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Propagates transport errors from the Forgejo search call or JSON
|
||||||
|
/// serialisation errors when emitting `--json` output.
|
||||||
|
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||||
|
let limit = u32::try_from(args.limit.min(u32::MAX as u64)).unwrap_or(u32::MAX);
|
||||||
|
let results = client
|
||||||
|
.api()
|
||||||
|
.repo_search(RepoSearchQuery {
|
||||||
|
q: args.query,
|
||||||
|
topic: args.topic.then_some(true),
|
||||||
|
include_desc: args.include_desc.then_some(true),
|
||||||
|
// All other filters left at defaults — callers who need them
|
||||||
|
// can drop to `--json` and post-filter.
|
||||||
|
uid: None,
|
||||||
|
priority_owner_id: None,
|
||||||
|
team_id: None,
|
||||||
|
starred_by: None,
|
||||||
|
private: None,
|
||||||
|
is_private: None,
|
||||||
|
template: None,
|
||||||
|
archived: None,
|
||||||
|
mode: None,
|
||||||
|
exclusive: None,
|
||||||
|
sort: None,
|
||||||
|
order: None,
|
||||||
|
})
|
||||||
|
.page_size(limit)
|
||||||
|
.send()?;
|
||||||
|
|
||||||
|
let repos = results.data.unwrap_or_default();
|
||||||
|
if client.json_mode() {
|
||||||
|
return print_json(&serde_json::to_value(&repos)?);
|
||||||
|
}
|
||||||
|
if repos.is_empty() {
|
||||||
|
eprintln!("(no results)");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
for repo in &repos {
|
||||||
|
let full_name = repo.full_name.as_deref().unwrap_or("?/?");
|
||||||
|
let desc = repo.description.as_deref().unwrap_or("");
|
||||||
|
if desc.is_empty() {
|
||||||
|
println!("{full_name}");
|
||||||
|
} else {
|
||||||
|
println!("{full_name} {desc}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue