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`
|
||||
/// emits the full label objects (id, name, color, description).
|
||||
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).
|
||||
Lint(verbs::lint::Args),
|
||||
/// 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::RepoAddCollaborator(a) => verbs::repo_add_collaborator::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::List(a) => verbs::list::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_create;
|
||||
pub mod repo_labels;
|
||||
pub mod repo_search;
|
||||
pub mod subscription;
|
||||
pub mod timeline;
|
||||
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