use anyhow::Result;
use clap::Args;
use serde::Serialize;
use std::path::PathBuf;
use dkp_core::{Pack, registry::types::SearchResult as RegistrySearchResult, search::SearchIndex};
use comfy_table::{Table, presets::UTF8_FULL};
use crate::{
cli::CmdCtx,
cmd::registry::account::{load_credentials_from_ctx, resolve_registry_url},
output::Render,
};
#[derive(Args, Debug)]
pub struct SearchArgs {
#[arg(trailing_var_arg = true, allow_hyphen_values = false)]
pub args: Vec<String>,
#[arg(long)]
pub registry: bool,
#[arg(long, value_name = "TYPE")]
pub r#type: Option<String>,
#[arg(long, default_value = "10")]
pub limit: usize,
#[arg(long, value_name = "NAME", requires = "registry")]
pub domain: Option<String>,
#[arg(long, value_name = "LEVEL", requires = "registry")]
pub conformance: Option<String>,
}
#[derive(Debug, Serialize)]
struct SearchResults {
query: String,
results: Vec<dkp_core::search::SearchResult>,
}
impl Render for SearchResults {
fn render_plain(&self) -> String {
if self.results.is_empty() {
return format!("No results for \"{}\"\n", self.query);
}
let mut out = String::new();
for (i, r) in self.results.iter().enumerate() {
out.push_str(&format!(
"[{}] {} (score: {:.2})\n …{}\n Source: {}\n\n",
r.id, r.title, r.score, r.excerpt, r.source_file
));
let _ = i;
}
out
}
fn render_table(&self) -> String {
if self.results.is_empty() {
return format!("No results for \"{}\"\n", self.query);
}
let mut table = Table::new();
table.load_preset(UTF8_FULL);
table.set_header(["ID", "Type", "Title", "Score", "Excerpt"]);
for r in &self.results {
table.add_row([
&r.id,
&r.asset_type,
&r.title,
&format!("{:.2}", r.score),
&r.excerpt,
]);
}
table.to_string()
}
}
#[derive(Debug, Serialize)]
struct RegistrySearchResults {
query: String,
total: u64,
results: Vec<RegistrySearchResult>,
}
impl Render for RegistrySearchResults {
fn render_plain(&self) -> String {
if self.results.is_empty() {
return format!("No registry results for \"{}\"\n", self.query);
}
let mut out = format!(
"{} result(s) for \"{}\" (showing {}):\n\n",
self.total,
self.query,
self.results.len()
);
for r in &self.results {
out.push_str(&format!(
"{}@{} [{}]\n {}\n Domain: {} Tags: {}\n\n",
r.name,
r.version,
r.conformance,
r.description.as_deref().unwrap_or(""),
r.domain,
r.tags.join(", "),
));
}
out
}
fn render_table(&self) -> String {
if self.results.is_empty() {
return format!("No registry results for \"{}\"\n", self.query);
}
let mut table = Table::new();
table.load_preset(UTF8_FULL);
table.set_header(["Name", "Version", "Domain", "Conformance", "Description"]);
for r in &self.results {
table.add_row([
&r.name,
&r.version,
&r.domain,
&r.conformance,
r.description.as_deref().unwrap_or(""),
]);
}
table.to_string()
}
}
pub async fn run(args: SearchArgs, cli: &CmdCtx) -> Result<()> {
if args.registry {
if args.args.is_empty() {
anyhow::bail!("query required (usage: dkp search --registry <query>)");
}
let query = args.args.join(" ");
let base = resolve_registry_url(&cli.config.registry.url);
let token = load_credentials_from_ctx(&cli.config.registry.url)
.ok()
.flatten()
.map(|(_, t)| t);
let client = dkp_core::registry::RegistryClient::new(base, token);
let resp = client
.search(
&query,
args.domain.as_deref(),
args.conformance.as_deref(),
args.limit as u32,
0,
)
.await?;
RegistrySearchResults {
query,
total: resp.total,
results: resp.results,
}
.print(cli.output);
return Ok(());
}
let mut positional = args.args.into_iter();
let pack_path: PathBuf = positional.next().map(PathBuf::from).ok_or_else(|| {
anyhow::anyhow!("pack path required for local search (usage: dkp search <pack> <query>)")
})?;
let query = positional
.next()
.ok_or_else(|| anyhow::anyhow!("query required (usage: dkp search <pack> <query>)"))?;
let pack = Pack::open(&pack_path)?;
let index = SearchIndex::build(&pack)?;
let fetch_limit = if args.r#type.is_some() {
args.limit * 10
} else {
args.limit
};
let mut results = index.search(&query, fetch_limit)?;
if let Some(ref type_filter) = args.r#type {
results.retain(|r| r.asset_type == type_filter.as_str());
results.truncate(args.limit);
}
SearchResults { query, results }.print(cli.output);
Ok(())
}