use crate ::prelude :: *;
use std ::fs;
use std ::path :: { Path, PathBuf };
type Result< T > = std ::result ::Result< T, Box<dyn std ::error ::Error >>;
#[ derive(Debug, Clone) ]
pub struct DocumentationConfig
{
pub file_path: PathBuf,
pub section_marker: String,
pub add_timestamp: bool,
pub create_backup: bool,
}
impl DocumentationConfig
{
pub fn readme_performance(readme_path: impl AsRef< Path >) -> Self
{
Self
{
file_path: readme_path.as_ref().to_path_buf(),
section_marker: "## Performance".to_string(),
add_timestamp: true,
create_backup: true,
}
}
pub fn benchmark_results(file_path: impl AsRef< Path >, section: &str) -> Self
{
Self
{
file_path: file_path.as_ref().to_path_buf(),
section_marker: section.to_string(),
add_timestamp: true,
create_backup: false,
}
}
}
#[ derive(Debug) ]
pub struct DocumentationUpdater
{
config: DocumentationConfig,
}
impl DocumentationUpdater
{
pub fn new(config: DocumentationConfig) -> Self
{
Self { config }
}
pub fn update_section(&self, new_content: &str) -> Result< DocumentationDiff >
{
let original_content = if self.config.file_path.exists()
{
fs ::read_to_string(&self.config.file_path)?
}
else
{
String ::new()
};
if self.config.create_backup && self.config.file_path.exists()
{
let backup_path = self.config.file_path.with_extension("md.backup");
fs ::copy(&self.config.file_path, &backup_path)?;
}
let timestamped_content = if self.config.add_timestamp
{
let timestamp = chrono ::Utc ::now().format("%Y-%m-%d %H: %M: %S UTC");
format!("< !-- Last updated: {} -- >\n\n{}", timestamp, new_content)
}
else
{
new_content.to_string()
};
let updated_content = self.replace_section(&original_content, ×tamped_content)?;
fs ::write(&self.config.file_path, &updated_content)?;
Ok(DocumentationDiff
{
file_path: self.config.file_path.clone(),
old_content: original_content,
new_content: updated_content,
section_marker: self.config.section_marker.clone(),
})
}
fn replace_section(&self, content: &str, new_section_content: &str) -> Result< String >
{
let lines: Vec< &str > = content.lines().collect();
let mut result = Vec ::new();
let mut in_target_section = false;
let mut section_found = false;
let mut start_idx = 0;
if lines.first().map_or(false, |line| line.starts_with("<!--"))
{
start_idx = lines.iter().position(|line| !line.trim().is_empty() && !line.starts_with("<!--"))
.unwrap_or(1);
}
for (_i, line) in lines.iter().enumerate().skip(start_idx)
{
if line.starts_with(&self.config.section_marker)
{
in_target_section = true;
section_found = true;
result.push(new_section_content);
}
else if in_target_section && line.starts_with("## ")
{
in_target_section = false;
result.push(line);
}
else if !in_target_section
{
result.push(line);
}
}
if !section_found
{
if !result.is_empty()
{
result.push("");
}
result.push(new_section_content);
}
Ok(result.join("\n"))
}
}
#[ derive(Debug) ]
pub struct DocumentationDiff
{
pub file_path: PathBuf,
pub old_content: String,
pub new_content: String,
pub section_marker: String,
}
impl DocumentationDiff
{
pub fn display_diff( &self )
{
println!("📄 Documentation Updated: {:?}", self.file_path.file_name().unwrap_or_default());
println!("═══════════════════════════");
let old_lines: Vec< &str > = self.old_content.lines().collect();
let new_lines: Vec< &str > = self.new_content.lines().collect();
let mut changes_found = false;
let max_lines = old_lines.len().max(new_lines.len());
for i in 0..max_lines.min(50) {
let old_line = old_lines.get(i).unwrap_or(&"");
let new_line = new_lines.get(i).unwrap_or(&"");
if old_line != new_line
{
changes_found = true;
if !old_line.is_empty()
{
println!("- {}", old_line);
}
if !new_line.is_empty()
{
println!("+ {}", new_line);
}
}
}
if !changes_found
{
println!(" (No visible changes - timestamps updated)");
}
if max_lines > 50
{
println!(" ... ({} more lines)", max_lines - 50);
}
println!("═══════════════════════════");
}
}
#[ derive(Debug) ]
pub struct BenchmarkDocumentationGenerator;
impl BenchmarkDocumentationGenerator
{
pub fn generate_comparison_table(
title: &str,
results: &[ (String, BenchmarkResult)],
) -> String
{
let mut output = String ::new();
output.push_str(&format!("### {} Performance\n\n", title));
output.push_str("| Algorithm | Mean Time | Ops/sec | Min | Max | Relative |\n");
output.push_str("|-----------|-----------|---------|-----|-----|----------|\n");
let fastest_ops = results.iter()
.map(|(_, result)| result.operations_per_second())
.fold(0.0f64, f64 ::max);
for (name, result) in results
{
let ops_per_sec = result.operations_per_second();
let relative = if ops_per_sec > 0.0
{
format!("{:.1}x", fastest_ops / ops_per_sec)
}
else
{
"N/A".to_string()
};
let relative_marker = if (ops_per_sec - fastest_ops).abs() < 1000.0
{
"**Fastest**"
}
else
{
&relative
};
output.push_str(&format!(
"| {} | {:.2?} | {:.0} | {:.2?} | {:.2?} | {} |\n",
name,
result.mean_time(),
ops_per_sec,
result.times.iter().min().unwrap_or(&std ::time ::Duration ::ZERO),
result.times.iter().max().unwrap_or(&std ::time ::Duration ::ZERO),
relative_marker
));
}
output
}
pub fn generate_scaling_table(
operation_name: &str,
scaling_results: &[ (usize, BenchmarkResult)],
) -> String
{
let mut output = String ::new();
output.push_str(&format!("### {} Scaling Performance\n\n", operation_name));
output.push_str("| Scale | Mean Time | Ops/sec | Memory Est. | Complexity |\n");
output.push_str("|-------|-----------|---------|-------------|------------|\n");
for (scale, result) in scaling_results
{
let scale_display = if *scale >= 1000
{
format!("{}K", scale / 1000)
}
else
{
scale.to_string()
};
let memory_est = format!("~{:.1} KB", (*scale as f64) * 0.001);
let complexity_hint = if scaling_results.len() > 1
{
"O(n)"
}
else
{
"Unknown"
};
output.push_str(&format!(
"| {} | {:.2?} | {:.0} | {} | {} |\n",
scale_display,
result.mean_time(),
result.operations_per_second(),
memory_est,
complexity_hint
));
}
output
}
}
pub fn update_readme_with_benchmarks< P: AsRef<Path >>(
readme_path: P,
benchmark_results: &[ (String, BenchmarkResult)],
) -> Result< () >
{
let config = DocumentationConfig ::readme_performance(readme_path);
let updater = DocumentationUpdater ::new(config);
let content = BenchmarkDocumentationGenerator ::generate_comparison_table(
"Latest Benchmark Results",
benchmark_results,
);
let diff = updater.update_section(&content)?;
diff.display_diff();
Ok(())
}