//! `repo-search [--query ] [--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, /// 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(u64::from(u32::MAX))).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(()) }