use super::api::GraphApi;
use ryo_analysis::{SummaryOptions, SymbolKind, ToSummary};
pub struct SummaryBuilder<'a> {
api: &'a GraphApi,
options: SummaryOptions,
}
impl<'a> SummaryBuilder<'a> {
pub fn new(api: &'a GraphApi) -> Self {
Self {
api,
options: SummaryOptions::default(),
}
}
pub fn detailed(mut self) -> Self {
self.options = SummaryOptions::detailed();
self
}
pub fn compact(mut self) -> Self {
self.options = SummaryOptions::compact();
self
}
pub fn max_items(mut self, max: usize) -> Self {
self.options = self.options.with_max_depth(max);
self
}
pub fn build(self) -> String {
self.api.to_summary_with_options(&self.options)
}
}
impl ToSummary for GraphApi {
fn to_summary_with_options(&self, opts: &SummaryOptions) -> String {
let mut out = String::new();
let stats = self.stats();
out.push_str("=== CodeGraph ===\n");
if opts.include_stats {
out.push_str(&format!("Nodes: {}\n", stats.nodes));
out.push_str(&format!("Edges: {}\n", stats.edges));
out.push_str(&format!(
"Types: {} functions, {} structs, {} enums, {} traits, {} impls\n",
stats.functions, stats.structs, stats.enums, stats.traits, stats.impls
));
out.push_str(&format!("Files: {}\n", stats.files));
} else if !opts.compact {
out.push_str(&format!(
"Nodes: {}, Edges: {}, Files: {}\n",
stats.nodes, stats.edges, stats.files
));
}
out.push('\n');
let max_items = opts.max_depth.unwrap_or(50);
let functions = self.query_nodes(SymbolKind::Function);
if !functions.is_empty() {
out.push_str("[Functions]\n");
for node in functions.iter().take(max_items) {
let vis = if node.is_public { "pub " } else { "" };
if opts.compact {
out.push_str(&format!(" {}fn {}\n", vis, node.name));
} else if opts.include_locations {
out.push_str(&format!(
" {}fn {} @ {} [{:?}]\n",
vis,
node.name,
node.file.display(),
node.symbol_id
));
} else {
out.push_str(&format!(
" {}fn {} [{:?}]\n",
vis, node.name, node.symbol_id
));
}
}
if functions.len() > max_items {
out.push_str(&format!(" ... and {} more\n", functions.len() - max_items));
}
out.push('\n');
}
let structs = self.query_nodes(SymbolKind::Struct);
if !structs.is_empty() {
out.push_str("[Structs]\n");
for node in structs.iter().take(max_items) {
let vis = if node.is_public { "pub " } else { "" };
if opts.compact {
out.push_str(&format!(" {}struct {}\n", vis, node.name));
} else if opts.include_locations {
out.push_str(&format!(
" {}struct {} @ {} [{:?}]\n",
vis,
node.name,
node.file.display(),
node.symbol_id
));
} else {
out.push_str(&format!(
" {}struct {} [{:?}]\n",
vis, node.name, node.symbol_id
));
}
}
if structs.len() > max_items {
out.push_str(&format!(" ... and {} more\n", structs.len() - max_items));
}
out.push('\n');
}
let enums = self.query_nodes(SymbolKind::Enum);
if !enums.is_empty() {
out.push_str("[Enums]\n");
for node in enums.iter().take(max_items) {
let vis = if node.is_public { "pub " } else { "" };
if opts.compact {
out.push_str(&format!(" {}enum {}\n", vis, node.name));
} else {
out.push_str(&format!(
" {}enum {} [{:?}]\n",
vis, node.name, node.symbol_id
));
}
}
out.push('\n');
}
let traits = self.query_nodes(SymbolKind::Trait);
if !traits.is_empty() {
out.push_str("[Traits]\n");
for node in traits.iter().take(max_items) {
let vis = if node.is_public { "pub " } else { "" };
if opts.compact {
out.push_str(&format!(" {}trait {}\n", vis, node.name));
} else {
out.push_str(&format!(
" {}trait {} [{:?}]\n",
vis, node.name, node.symbol_id
));
}
}
out.push('\n');
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
fn create_test_project() -> tempfile::TempDir {
let dir = tempdir().unwrap();
let src = dir.path().join("src");
fs::create_dir(&src).unwrap();
fs::write(
dir.path().join("Cargo.toml"),
r#"[package]
name = "test-project"
version = "0.1.0"
edition = "2021"
"#,
)
.unwrap();
fs::write(
src.join("lib.rs"),
r#"
pub fn greet() {}
pub struct User {}
pub enum Status { Active }
pub trait Greetable {}
"#,
)
.unwrap();
dir
}
#[test]
fn test_to_summary() {
let dir = create_test_project();
let api = GraphApi::from_path(dir.path()).unwrap();
let summary = SummaryBuilder::new(&api).build();
assert!(summary.contains("CodeGraph"));
assert!(summary.contains("Nodes"));
}
#[test]
fn test_summary_detailed() {
let dir = create_test_project();
let api = GraphApi::from_path(dir.path()).unwrap();
let summary = SummaryBuilder::new(&api).detailed().build();
assert!(summary.contains("functions"));
assert!(summary.contains("structs"));
}
#[test]
fn test_summary_compact() {
let dir = create_test_project();
let api = GraphApi::from_path(dir.path()).unwrap();
let summary = SummaryBuilder::new(&api).compact().build();
assert!(summary.contains("CodeGraph"));
}
}