use anyhow::Result;
use chrono::Utc;
use std::fs;
use crate::config::Config;
use crate::parser::RustParser;
use crate::scanner::{FileType, ScanResult};
use crate::token_counter::{ContentPrioritizer, ContentSection};
pub struct ContextGenerator {
config: Config,
prioritizer: ContentPrioritizer,
}
impl ContextGenerator {
pub fn new(config: Config) -> Self {
Self {
config,
prioritizer: ContentPrioritizer::new()
.expect("Failed to initialize content prioritizer"),
}
}
pub async fn generate_context(&self, scan_result: ScanResult) -> Result<()> {
let mut sections = Vec::new();
sections.push(self.create_metadata_section(&scan_result));
sections.push(self.create_structure_section(&scan_result));
sections.extend(self.create_markdown_sections(&scan_result));
sections.extend(self.create_rust_analysis_sections(&scan_result).await?);
sections.extend(self.create_source_code_sections(&scan_result));
let final_sections = self
.prioritizer
.prioritize_content(sections, self.config.max_tokens);
let context = self.format_context(final_sections);
fs::write(&self.config.output_file, context)?;
println!(
"Context generated successfully in: {}",
self.config.output_file
);
Ok(())
}
fn create_metadata_section(&self, scan_result: &ScanResult) -> ContentSection {
let mut content = String::new();
content.push_str("# Project Metadata\n\n");
content.push_str(&format!("**Name:** {}\n", scan_result.metadata.name));
if let Some(description) = &scan_result.metadata.description {
content.push_str(&format!("**Description:** {description}\n"));
}
if !scan_result.metadata.dependencies.is_empty() {
content.push_str("**Dependencies:**\n");
for dep in &scan_result.metadata.dependencies {
content.push_str(&format!("- {dep}\n"));
}
}
if let Some(rust_version) = &scan_result.metadata.rust_version {
content.push_str(&format!("**Version:** {rust_version}\n"));
}
content.push_str(&format!(
"**Total files:** {}\n",
scan_result.project_structure.total_files
));
content.push_str(&format!(
"**Total size:** {} bytes\n\n",
scan_result.project_structure.total_size
));
ContentSection {
title: "Project Metadata".to_string(),
content,
priority: 10,
truncated: false,
}
}
fn create_structure_section(&self, scan_result: &ScanResult) -> ContentSection {
let mut content = String::new();
content.push_str("# Project Structure\n\n");
content.push_str(&scan_result.project_structure.tree);
content.push('\n');
ContentSection {
title: "Project Structure".to_string(),
content,
priority: 9,
truncated: false,
}
}
fn create_markdown_sections(&self, scan_result: &ScanResult) -> Vec<ContentSection> {
let mut sections = Vec::new();
for file in &scan_result.files {
if matches!(file.file_type, FileType::Markdown) {
let mut content = String::new();
content.push_str(&format!(
"# Documentation: {}\n\n",
file.relative_path.display()
));
content.push_str(&file.content);
content.push('\n');
sections.push(ContentSection {
title: format!("Documentation: {}", file.relative_path.display()),
content,
priority: 8,
truncated: false,
});
}
}
sections
}
async fn create_rust_analysis_sections(
&self,
scan_result: &ScanResult,
) -> Result<Vec<ContentSection>> {
let mut sections = Vec::new();
for file in &scan_result.files {
if matches!(file.file_type, FileType::Rust) {
match RustParser::parse_rust_file(&file.path.to_string_lossy(), &file.content) {
Ok(analysis) => {
let mut content = String::new();
content.push_str(&format!(
"# Rust Analysis: {}\n\n",
file.relative_path.display()
));
if !analysis.modules.is_empty() {
content.push_str("## Modules\n");
for module in &analysis.modules {
content.push_str(&format!(
"- **{}**: {}\n",
module.name, module.visibility
));
}
content.push('\n');
}
if !analysis.functions.is_empty() {
content.push_str("## Functions\n");
for function in &analysis.functions {
let params = function.parameters.join(", ");
let return_type = function.return_type.as_deref().unwrap_or("()");
content.push_str(&format!(
"- **{}**({}) -> {} ({})\n",
function.name, params, return_type, function.visibility
));
}
content.push('\n');
}
if !analysis.structs.is_empty() {
content.push_str("## Structs\n");
for struct_info in &analysis.structs {
content.push_str(&format!(
"- **{}**: {} fields ({})\n",
struct_info.name,
struct_info.fields.len(),
struct_info.visibility
));
}
content.push('\n');
}
if !analysis.enums.is_empty() {
content.push_str("## Enums\n");
for enum_info in &analysis.enums {
content.push_str(&format!(
"- **{}**: {} variants ({})\n",
enum_info.name,
enum_info.variants.len(),
enum_info.visibility
));
}
content.push('\n');
}
if !analysis.implementations.is_empty() {
content.push_str("## Implementations\n");
for impl_info in &analysis.implementations {
content.push_str(&format!(
"- **impl {}**: {} methods\n",
impl_info.target,
impl_info.methods.len()
));
}
content.push('\n');
}
sections.push(ContentSection {
title: format!("Rust Analysis: {}", file.relative_path.display()),
content,
priority: 6,
truncated: false,
});
}
Err(e) => {
eprintln!(
"Warning: Failed to parse {}: {}",
file.relative_path.display(),
e
);
}
}
}
}
Ok(sections)
}
fn create_source_code_sections(&self, scan_result: &ScanResult) -> Vec<ContentSection> {
let mut sections = Vec::new();
for file in &scan_result.files {
let mut content = String::new();
content.push_str(&format!("# Source: {}\n\n", file.relative_path.display()));
content.push_str("```");
match file.file_type {
FileType::Rust => content.push_str("rust"),
FileType::Markdown => content.push_str("markdown"),
}
content.push('\n');
content.push_str(&file.content);
content.push_str("\n```\n\n");
sections.push(ContentSection {
title: format!("Source: {}", file.relative_path.display()),
content,
priority: 3,
truncated: false,
});
}
sections
}
fn format_context(&self, sections: Vec<ContentSection>) -> String {
let mut context = String::new();
context.push_str("# AI Context Generation Report\n\n");
context.push_str(&format!(
"Generated on: {}\n",
Utc::now().format("%Y-%m-%d %H:%M:%S UTC")
));
context.push_str(&format!(
"Repository: {}\n",
self.config.repo_path.display()
));
context.push_str(&format!("Max tokens: {}\n\n", self.config.max_tokens));
context.push_str("## Table of Contents\n\n");
for (i, section) in sections.iter().enumerate() {
context.push_str(&format!("{}. {}", i + 1, section.title));
if section.truncated {
context.push_str(" (truncated)");
}
context.push('\n');
}
context.push('\n');
for section in sections {
context.push_str("---\n\n");
context.push_str(§ion.content);
}
context
}
}