use crate::error::Result;
use crate::storage::mapping::StorageManager;
use crate::git::diff::DiffExtractor;
use crate::ai::{AIClient, prompts::PromptTemplates};
use std::fs;
pub async fn execute(
commit: Option<String>,
pr: Option<u32>,
staged: bool,
service: String,
section: Option<String>,
dry_run: bool,
) -> Result<()> {
tracing::info!("Updating documentation for service: {}", service);
// Get service mapping
let storage = StorageManager::new()?;
let mapping = storage.get_mapping(&service)?;
if mapping.docs.is_empty() {
return Err(crate::error::KtmeError::DocumentNotFound(
format!("No documentation locations mapped for service: {}", service)
));
}
// Extract changes
let diff = if let Some(commit_ref) = commit {
tracing::info!("Using commit: {}", commit_ref);
let extractor = DiffExtractor::new("commit".to_string(), commit_ref, None)?;
extractor.extract()?
} else if let Some(pr_number) = pr {
tracing::info!("Using PR: #{}", pr_number);
return Err(crate::error::KtmeError::UnsupportedProvider(
"PR-based updates are not yet implemented".to_string()
));
} else if staged {
tracing::info!("Using staged changes");
let extractor = DiffExtractor::new("staged".to_string(), "staged".to_string(), None)?;
extractor.extract()?
} else {
return Err(crate::error::KtmeError::InvalidInput(
"No source specified. Use --commit, --pr, or --staged".to_string()
));
};
if dry_run {
println!("Dry run mode - would update the following locations:");
for doc in &mapping.docs {
println!(" - {} ({})", doc.location, doc.r#type);
}
println!("Changes to apply:");
println!(" Source: {}", diff.identifier);
println!(" Files: {}", diff.summary.total_files);
return Ok(());
}
// Generate update content
let ai_client = AIClient::new()?;
let prompt = PromptTemplates::update_documentation_prompt(&diff, section.as_deref())?;
tracing::info!("Generating update content...");
let update_content = ai_client.generate_documentation(&prompt).await?;
// Apply updates to each documentation location
for doc_location in &mapping.docs {
match doc_location.r#type.as_str() {
"markdown" => {
update_markdown_file(&doc_location.location, &update_content, section.as_deref())?;
println!("✓ Updated markdown file: {}", doc_location.location);
}
"confluence" => {
// TODO: Implement Confluence update
println!("⚠ Confluence updates not yet implemented: {}", doc_location.location);
}
_ => {
println!("⚠ Unknown documentation type: {}", doc_location.r#type);
}
}
}
println!("Documentation updated successfully!");
Ok(())
}
fn update_markdown_file(file_path: &str, content: &str, section: Option<&str>) -> Result<()> {
let existing_content = fs::read_to_string(file_path)
.map_err(|e| crate::error::KtmeError::Io(e))?;
let updated_content = if let Some(section_name) = section {
// Find and update specific section
update_markdown_section(&existing_content, section_name, content)
} else {
// Append to end of file
format!("{}\n\n---\n\n## Update {}\n\n{}",
existing_content,
chrono::Utc::now().format("%Y-%m-%d"),
content)
};
fs::write(file_path, updated_content)
.map_err(|e| crate::error::KtmeError::Io(e))?;
Ok(())
}
fn update_markdown_section(content: &str, section_name: &str, new_content: &str) -> String {
let lines: Vec<&str> = content.lines().collect();
let mut result: Vec<String> = Vec::new();
let mut in_section = false;
let mut section_found = false;
for line in lines {
if line.starts_with("#") && line.to_lowercase().contains(§ion_name.to_lowercase()) {
in_section = true;
section_found = true;
result.push(line.to_string());
result.push("".to_string());
result.extend(new_content.lines().map(|l| l.to_string()));
} else if in_section && line.starts_with("#") {
in_section = false;
result.push(line.to_string());
} else if !in_section {
result.push(line.to_string());
}
}
if !section_found {
// Section not found, append to end
result.push("".to_string());
result.push(format!("## {}", section_name));
result.push("".to_string());
result.extend(new_content.lines().map(|l| l.to_string()));
}
result.join("\n")
}