use phrona::{Category, EngineReport, ResultItem, SearchResponse};
pub fn result_line(kind: &str, position: usize, title: &str, url: &str, meta: &str) {
println!(
"{position:>3}. [{kind:6}] {title}\n {url}{}",
if meta.is_empty() {
String::new()
} else {
format!("\n {meta}")
}
);
}
pub fn print_response(r: &SearchResponse) {
println!(
"query: {}\ncategory: {} | page: {} | results: {} | elapsed: {} ms",
r.query,
r.category.as_str(),
r.page,
r.total,
r.elapsed_ms
);
if let Some(answer) = r.answer.as_deref().filter(|a| !a.trim().is_empty()) {
println!("\nanswer: {answer}");
}
if !r.suggestions.is_empty() {
println!("\nsuggestions: {}", r.suggestions.join(" | "));
}
for (i, item) in r.results.iter().enumerate() {
print_item(i + 1, item);
}
if !r.engines.is_empty() {
println!("\nengines:");
for e in &r.engines {
println!(
" {:16} {:6} {:>4} results{}",
e.name,
e.status,
e.results,
e.error
.as_deref()
.map(|err| format!(" ({err})"))
.unwrap_or_default()
);
}
}
}
pub fn print_item(position: usize, item: &ResultItem) {
match item {
ResultItem::Web(w) => result_line("web", position, &w.title, &w.url, &w.description),
ResultItem::Image(i) => result_line(
"image",
position,
&i.title,
&i.url,
&format!("{} ({}x{})", i.image_url, i.width, i.height),
),
ResultItem::News(n) => result_line(
"news",
position,
&n.title,
&n.url,
&format!(
"{}{}",
n.source,
n.published
.as_deref()
.map(|p| format!(" - {p}"))
.unwrap_or_default()
),
),
ResultItem::Video(v) => result_line(
"video",
position,
&v.title,
&v.url,
&format!(
"{} | {} views | {}{}",
v.uploader,
v.views,
v.duration,
v.published
.as_deref()
.map(|p| format!(" | {p}"))
.unwrap_or_default()
),
),
ResultItem::Book(b) => result_line(
"book",
position,
&b.title,
&b.url,
&format!("{} | {}{}", b.author, b.publisher, b.info),
),
}
}
pub fn print_engines_table(category: Category) {
let names: Vec<String> = phrona::available_engines(category)
.iter()
.map(|e| e.name.clone())
.collect();
println!(
"{} ({}): {}",
category.as_str(),
names.len(),
names.join(", ")
);
}
pub fn print_test_report(reports: Vec<(Category, SearchResponse)>) {
let mut matrix: Vec<EngineReport> = Vec::new();
let mut printed = std::collections::BTreeSet::new();
for (cat, resp) in &reports {
println!(
"category: {:8} total: {:>3} elapsed: {:>4} ms answer: {}",
cat.as_str(),
resp.total,
resp.elapsed_ms,
resp.answer.as_deref().is_some_and(|a| !a.is_empty())
);
for e in &resp.engines {
matrix.push(e.clone());
}
}
println!("\navailability matrix:");
for e in &matrix {
if printed.insert(e.name.clone()) {
println!(
" {:16} {:6}",
e.name,
if e.status == "ok" {
"OK".to_string()
} else {
e.status.clone()
}
);
}
}
}
pub fn print_grounded(query: &str, resp: &SearchResponse, max_results: usize) {
let answer = resp
.answer
.clone()
.unwrap_or_else(|| format!("Found {} sources for \"{query}\".", resp.total));
println!("query: {query}\nanswer: {answer}");
let mut shown = 0;
for (i, item) in resp.results.iter().enumerate() {
if shown >= max_results {
break;
}
let (title, url, content) = match item {
ResultItem::Web(w) => (&w.title, &w.url, &w.description.as_str()),
ResultItem::News(n) => (&n.title, &n.url, &n.description.as_str()),
ResultItem::Video(v) => (&v.title, &v.url, &v.description.as_str()),
ResultItem::Image(im) => (&im.title, &im.url, &im.source.as_str()),
ResultItem::Book(b) => (&b.title, &b.url, &b.info.as_str()),
};
if content.trim().is_empty() {
continue;
}
println!("\n{}. {title}\n {url}\n {content}", i + 1);
shown += 1;
}
}