use crate::ansi_colors::Colorize;
use super::pmat_query_fusion::{compute_quality_summary, format_summary_line};
use super::pmat_query_types::{FusedResult, PmatQueryResult};
use crate::cli::oracle::types::OracleOutputFormat;
pub(super) fn parse_pmat_query_output(json: &str) -> anyhow::Result<Vec<PmatQueryResult>> {
let results: Vec<PmatQueryResult> = serde_json::from_str(json)
.map_err(|e| anyhow::anyhow!("Failed to parse pmat query output: {e}"))?;
Ok(results)
}
pub(super) fn run_pmat_query(
opts: &super::pmat_query_types::PmatQueryOptions,
) -> anyhow::Result<Vec<PmatQueryResult>> {
use super::pmat_query_cache::{load_cached_results, save_cache};
if let Some(cached) = load_cached_results(&opts.query, opts.project_path.as_deref()) {
eprintln!(" {} hit \u{2014} using cached results", "[ cache]".dimmed());
return Ok(cached);
}
let limit_str = opts.limit.to_string();
let mut args: Vec<&str> = vec!["query", &opts.query, "--format", "json", "--limit", &limit_str];
let grade_val;
if let Some(ref grade) = opts.min_grade {
grade_val = grade.clone();
args.push("--min-grade");
args.push(&grade_val);
}
let complexity_str;
if let Some(max) = opts.max_complexity {
complexity_str = max.to_string();
args.push("--max-complexity");
args.push(&complexity_str);
}
if opts.include_source {
args.push("--include-source");
}
let working_dir = opts.project_path.as_ref().map(std::path::Path::new);
let output = crate::tools::run_tool("pmat", &args, working_dir)?;
let results = parse_pmat_query_output(&output)?;
save_cache(&opts.query, opts.project_path.as_deref(), &results);
Ok(results)
}
pub(super) fn grade_badge(grade: &str) -> String {
match grade {
"A" => format!("[{}]", "A".bright_green().bold()),
"B" => format!("[{}]", "B".green()),
"C" => format!("[{}]", "C".bright_yellow()),
"D" => format!("[{}]", "D".yellow()),
"F" => format!("[{}]", "F".bright_red().bold()),
other => format!("[{}]", other.dimmed()),
}
}
pub(super) fn tdg_score_bar(score: f64, width: usize) -> String {
let filled = ((score / 100.0) * width as f64).round() as usize;
let empty = width.saturating_sub(filled);
format!("{}{} {:.1}", "\u{2588}".repeat(filled), "\u{2591}".repeat(empty), score)
}
fn print_quality_summary(results: &[PmatQueryResult]) {
if results.is_empty() {
return;
}
let summary = compute_quality_summary(results);
println!("{}: {}", "Summary".bright_yellow().bold(), format_summary_line(&summary));
println!();
}
fn pmat_format_results_text(query_text: &str, results: &[PmatQueryResult]) {
println!("{}: {}", "PMAT Query".bright_cyan(), query_text);
println!("{}", "\u{2500}".repeat(50).dimmed());
println!();
for (i, r) in results.iter().enumerate() {
let badge = grade_badge(&r.tdg_grade);
let score_bar = tdg_score_bar(r.tdg_score, 10);
let project_prefix =
r.project.as_ref().map(|p| format!("[{}] ", p.bright_blue())).unwrap_or_default();
println!(
"{}. {} {}{}:{} {} {}",
i + 1,
badge,
project_prefix,
r.file_path.cyan(),
r.start_line,
r.function_name.bright_yellow(),
score_bar
);
if !r.signature.is_empty() {
println!(" {}", r.signature.dimmed());
}
println!(" Complexity: {} | Big-O: {} | SATD: {}", r.complexity, r.big_o, r.satd_count);
if let Some(ref doc) = r.doc_comment {
let preview: String = doc.chars().take(120).collect();
println!(" {}", preview.dimmed());
}
if !r.rag_backlinks.is_empty() {
println!(" {} {}", "See also:".bright_green(), r.rag_backlinks.join(", ").dimmed());
}
if let Some(ref src) = r.source {
println!(" {}", "\u{2500}".repeat(40).dimmed());
for line in src.lines().take(10) {
#[cfg(feature = "syntect")]
crate::cli::syntax::print_highlighted_line(
line,
crate::cli::syntax::Language::Rust,
" ",
);
#[cfg(not(feature = "syntect"))]
println!(" {}", line);
}
if src.lines().count() > 10 {
println!(" {}", "...".dimmed());
}
}
println!();
}
print_quality_summary(results);
}
fn pmat_format_results_json(query_text: &str, results: &[PmatQueryResult]) -> anyhow::Result<()> {
let summary = compute_quality_summary(results);
let json = serde_json::json!({
"query": query_text,
"source": "pmat",
"result_count": results.len(),
"summary": {
"grades": summary.grades,
"avg_complexity": summary.avg_complexity,
"total_satd": summary.total_satd,
"complexity_range": [summary.complexity_range.0, summary.complexity_range.1],
},
"results": results,
});
println!("{}", serde_json::to_string_pretty(&json)?);
Ok(())
}
fn pmat_format_results_markdown(query_text: &str, results: &[PmatQueryResult]) {
println!("## PMAT Query Results\n");
println!("**Query:** {}\n", query_text);
println!("| # | Grade | File | Function | TDG | Complexity | Big-O |");
println!("|---|-------|------|----------|-----|------------|-------|");
for (i, r) in results.iter().enumerate() {
println!(
"| {} | {} | {}:{} | `{}` | {:.1} | {} | {} |",
i + 1,
r.tdg_grade,
r.file_path,
r.start_line,
r.function_name,
r.tdg_score,
r.complexity,
r.big_o
);
}
let summary = compute_quality_summary(results);
println!("\n**Summary:** {}", format_summary_line(&summary));
}
pub(super) fn pmat_display_results(
query_text: &str,
results: &[PmatQueryResult],
format: OracleOutputFormat,
) -> anyhow::Result<()> {
match format {
OracleOutputFormat::Json => pmat_format_results_json(query_text, results)?,
OracleOutputFormat::Markdown => pmat_format_results_markdown(query_text, results),
OracleOutputFormat::Text => pmat_format_results_text(query_text, results),
OracleOutputFormat::Code | OracleOutputFormat::CodeSvg => {
for r in results {
if let Some(ref src) = r.source {
println!("// {}:{} - {}", r.file_path, r.start_line, r.function_name);
println!("{}", src);
println!();
}
}
if results.iter().all(|r| r.source.is_none()) {
eprintln!("No source code in results (try --pmat-include-source)");
std::process::exit(1);
}
}
}
Ok(())
}
pub(super) fn pmat_display_combined(
query_text: &str,
pmat_results: &[PmatQueryResult],
rag_results: &[crate::oracle::rag::RetrievalResult],
format: OracleOutputFormat,
) -> anyhow::Result<()> {
use super::pmat_query_fusion::rrf_fuse_results;
let fused = rrf_fuse_results(pmat_results, rag_results, 20);
match format {
OracleOutputFormat::Json => display_combined_json(query_text, pmat_results, &fused)?,
OracleOutputFormat::Markdown => {
display_combined_markdown(query_text, pmat_results, &fused);
}
OracleOutputFormat::Text => display_combined_text(pmat_results, &fused),
OracleOutputFormat::Code | OracleOutputFormat::CodeSvg => display_combined_code(&fused),
}
Ok(())
}
fn display_combined_json(
query_text: &str,
pmat_results: &[PmatQueryResult],
fused: &[(FusedResult, f64)],
) -> anyhow::Result<()> {
let summary = compute_quality_summary(pmat_results);
let json = serde_json::json!({
"query": query_text,
"mode": "rrf_fused",
"k": 60,
"result_count": fused.len(),
"summary": {
"grades": summary.grades,
"avg_complexity": summary.avg_complexity,
"total_satd": summary.total_satd,
"complexity_range": [summary.complexity_range.0, summary.complexity_range.1],
},
"results": fused.iter().map(|(item, score)| {
let mut v = serde_json::to_value(item).unwrap_or_default();
if let Some(obj) = v.as_object_mut() {
obj.insert("rrf_score".to_string(), serde_json::json!(score));
}
v
}).collect::<Vec<_>>(),
});
println!("{}", serde_json::to_string_pretty(&json)?);
Ok(())
}
fn display_combined_markdown(
query_text: &str,
pmat_results: &[PmatQueryResult],
fused: &[(FusedResult, f64)],
) {
println!("## Combined PMAT + RAG Results (RRF-fused)\n");
println!("**Query:** {}\n", query_text);
println!("| # | Type | Source | Score |");
println!("|---|------|--------|-------|");
for (i, (item, score)) in fused.iter().enumerate() {
match item {
FusedResult::Function(r) => {
println!(
"| {} | fn | {}:{} `{}` [{}] | {:.3} |",
i + 1,
r.file_path,
r.start_line,
r.function_name,
r.tdg_grade,
score
);
}
FusedResult::Document { component, source, .. } => {
println!("| {} | doc | [{}] {} | {:.3} |", i + 1, component, source, score);
}
}
}
let summary = compute_quality_summary(pmat_results);
println!("\n**Summary (functions):** {}", format_summary_line(&summary));
}
fn display_combined_text(pmat_results: &[PmatQueryResult], fused: &[(FusedResult, f64)]) {
println!("{} (RRF k=60)", "Combined Search".bright_cyan().bold());
println!("{}", "\u{2500}".repeat(50).dimmed());
println!();
for (i, (item, score)) in fused.iter().enumerate() {
display_combined_text_item(i, item, *score);
}
print_quality_summary(pmat_results);
}
fn display_combined_text_item(i: usize, item: &FusedResult, score: f64) {
let score_pct = (score * 100.0) as usize;
let bar_filled = (score * 10.0).round() as usize;
let bar_empty = 10_usize.saturating_sub(bar_filled);
let bar = format!(
"{}{} {:3}%",
"\u{2588}".repeat(bar_filled),
"\u{2591}".repeat(bar_empty),
score_pct,
);
match item {
FusedResult::Function(r) => {
let badge = grade_badge(&r.tdg_grade);
let project_prefix =
r.project.as_ref().map(|p| format!("[{}] ", p.bright_blue())).unwrap_or_default();
println!(
"{}. {} {} {}{}:{} {} {}",
i + 1,
"[fn]".bright_cyan(),
badge,
project_prefix,
r.file_path.cyan(),
r.start_line,
r.function_name.bright_yellow(),
bar,
);
println!(
" Complexity: {} | Big-O: {} | SATD: {}",
r.complexity, r.big_o, r.satd_count
);
if !r.rag_backlinks.is_empty() {
println!(
" {} {}",
"See also:".bright_green(),
r.rag_backlinks.join(", ").dimmed()
);
}
}
FusedResult::Document { component, source, content, .. } => {
println!(
"{}. {} [{}] {} {}",
i + 1,
"[doc]".bright_green(),
component.bright_yellow(),
source.dimmed(),
bar,
);
if !content.is_empty() {
let preview: String = content.chars().take(200).collect();
println!(" {}", preview.dimmed());
}
}
}
println!();
}
fn display_combined_code(fused: &[(FusedResult, f64)]) {
for (item, _) in fused {
if let FusedResult::Function(r) = item {
if let Some(ref src) = r.source {
println!("// {}:{} - {}", r.file_path, r.start_line, r.function_name);
println!("{}", src);
println!();
}
}
}
let has_source = fused
.iter()
.any(|(item, _)| matches!(item, FusedResult::Function(r) if r.source.is_some()));
if !has_source {
eprintln!("No source code in results (try --pmat-include-source)");
std::process::exit(1);
}
}
pub(super) fn show_pmat_query_usage() {
println!("{}", "Usage: batuta oracle --pmat-query \"your query here\"".dimmed());
println!();
println!("{}", "Examples:".bright_yellow());
println!(" {} {}", "batuta oracle --pmat-query".cyan(), "\"error handling\"".dimmed());
println!(
" {} {}",
"batuta oracle --pmat-query".cyan(),
"\"serialize\" --pmat-min-grade A".dimmed()
);
println!(
" {} {}",
"batuta oracle --pmat-query".cyan(),
"\"allocator\" --pmat-include-source --format json".dimmed()
);
println!(
" {} {}",
"batuta oracle --pmat-query".cyan(),
"\"cache\" --rag # combined RRF-fused search".dimmed()
);
println!(
" {} {}",
"batuta oracle --pmat-query".cyan(),
"\"tokenizer\" --pmat-all-local # search all projects".dimmed()
);
}