The bead description mentioned compile errors in hash.rs from API drift, but those errors were either already fixed or misattributed. The API usage was already correct: - compute_fingerprint already takes 3 arguments with source - len() already propagates Result with ? - read_at method already used correctly - Catalog fields accessed via trailer correctly Only cleanup: removed unused std::fs::File and std::io imports. Verification: notes/bf-4mkhv.md
253 lines
7 KiB
Rust
253 lines
7 KiB
Rust
//! Calculate rustdoc coverage for pdftract-core.
|
|
//!
|
|
//! Counts public items and those with worked examples (```rust blocks).
|
|
|
|
use std::fs;
|
|
use std::path::Path;
|
|
|
|
#[derive(Debug, Default)]
|
|
struct CoverageStats {
|
|
total_modules: usize,
|
|
documented_modules: usize,
|
|
total_functions: usize,
|
|
documented_functions: usize,
|
|
total_structs: usize,
|
|
documented_structs: usize,
|
|
total_enums: usize,
|
|
documented_enums: usize,
|
|
total_traits: usize,
|
|
documented_traits: usize,
|
|
total_type_aliases: usize,
|
|
documented_type_aliases: usize,
|
|
total_consts: usize,
|
|
documented_consts: usize,
|
|
}
|
|
|
|
impl CoverageStats {
|
|
fn total_items(&self) -> usize {
|
|
self.total_modules
|
|
+ self.total_functions
|
|
+ self.total_structs
|
|
+ self.total_enums
|
|
+ self.total_traits
|
|
+ self.total_type_aliases
|
|
+ self.total_consts
|
|
}
|
|
|
|
fn documented_items(&self) -> usize {
|
|
self.documented_modules
|
|
+ self.documented_functions
|
|
+ self.documented_structs
|
|
+ self.documented_enums
|
|
+ self.documented_traits
|
|
+ self.documented_type_aliases
|
|
+ self.documented_consts
|
|
}
|
|
|
|
fn coverage_pct(&self) -> f64 {
|
|
if self.total_items() == 0 {
|
|
0.0
|
|
} else {
|
|
(self.documented_items() as f64 / self.total_items() as f64) * 100.0
|
|
}
|
|
}
|
|
}
|
|
|
|
fn has_worked_example(doc: &str) -> bool {
|
|
doc.contains("```rust")
|
|
}
|
|
|
|
fn analyze_file(path: &Path, stats: &mut CoverageStats) {
|
|
let content = match fs::read_to_string(path) {
|
|
Ok(c) => c,
|
|
Err(_) => return,
|
|
};
|
|
|
|
let lines: Vec<&str> = content.lines().collect();
|
|
let mut i = 0;
|
|
|
|
while i < lines.len() {
|
|
let line = lines[i];
|
|
|
|
// Skip private items and doc comments
|
|
if line.trim().starts_with("///") || line.trim().starts_with("//!") {
|
|
i += 1;
|
|
continue;
|
|
}
|
|
|
|
// Collect preceding doc comments
|
|
let mut doc = String::new();
|
|
let mut j = i;
|
|
while j > 0 && (lines[j - 1].trim().starts_with("///") || lines[j - 1].trim().starts_with("//!")) {
|
|
doc.push_str(lines[j - 1].trim());
|
|
doc.push('\n');
|
|
j -= 1;
|
|
}
|
|
|
|
let has_example = has_worked_example(&doc);
|
|
|
|
// Public modules (excluding re-exports)
|
|
if line.contains("pub mod") && !line.contains("pub use") {
|
|
stats.total_modules += 1;
|
|
if has_example {
|
|
stats.documented_modules += 1;
|
|
}
|
|
}
|
|
// Public functions
|
|
else if line.contains("pub fn") || line.contains("pub async fn") {
|
|
stats.total_functions += 1;
|
|
if has_example {
|
|
stats.documented_functions += 1;
|
|
}
|
|
}
|
|
// Public structs
|
|
else if line.contains("pub struct") {
|
|
stats.total_structs += 1;
|
|
if has_example {
|
|
stats.documented_structs += 1;
|
|
}
|
|
}
|
|
// Public enums
|
|
else if line.contains("pub enum") {
|
|
stats.total_enums += 1;
|
|
if has_example {
|
|
stats.documented_enums += 1;
|
|
}
|
|
}
|
|
// Public traits
|
|
else if line.contains("pub trait") {
|
|
stats.total_traits += 1;
|
|
if has_example {
|
|
stats.documented_traits += 1;
|
|
}
|
|
}
|
|
// Public type aliases
|
|
else if line.contains("pub type") {
|
|
stats.total_type_aliases += 1;
|
|
if has_example {
|
|
stats.documented_type_aliases += 1;
|
|
}
|
|
}
|
|
// Public constants
|
|
else if line.contains("pub const") || line.contains("pub static") {
|
|
stats.total_consts += 1;
|
|
if has_example {
|
|
stats.documented_consts += 1;
|
|
}
|
|
}
|
|
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
let src_dir = Path::new("crates/pdftract-core/src");
|
|
let mut stats = CoverageStats::default();
|
|
|
|
// Analyze all .rs files
|
|
for entry in walkdir::WalkDir::new(src_dir)
|
|
.into_iter()
|
|
.filter_map(|e| e.ok())
|
|
{
|
|
let path = entry.path();
|
|
if path.extension().map_or(false, |e| e == "rs") {
|
|
analyze_file(path, &mut stats);
|
|
}
|
|
}
|
|
|
|
println!("=== Rustdoc Coverage Report for pdftract-core ===\n");
|
|
println!("{:<25} {:>10} {:>10} {:>10}", "Category", "Total", "Documented", "Coverage");
|
|
println!("{}", "-" * 59);
|
|
println!(
|
|
"{:<25} {:>10} {:>10} {:>9.1}%",
|
|
"Modules",
|
|
stats.total_modules,
|
|
stats.documented_modules,
|
|
if stats.total_modules > 0 {
|
|
(stats.documented_modules as f64 / stats.total_modules as f64) * 100.0
|
|
} else {
|
|
0.0
|
|
}
|
|
);
|
|
println!(
|
|
"{:<25} {:>10} {:>10} {:>9.1}%",
|
|
"Functions",
|
|
stats.total_functions,
|
|
stats.documented_functions,
|
|
if stats.total_functions > 0 {
|
|
(stats.documented_functions as f64 / stats.total_functions as f64) * 100.0
|
|
} else {
|
|
0.0
|
|
}
|
|
);
|
|
println!(
|
|
"{:<25} {:>10} {:>10} {:>9.1}%",
|
|
"Structs",
|
|
stats.total_structs,
|
|
stats.documented_structs,
|
|
if stats.total_structs > 0 {
|
|
(stats.documented_structs as f64 / stats.total_structs as f64) * 100.0
|
|
} else {
|
|
0.0
|
|
}
|
|
);
|
|
println!(
|
|
"{:<25} {:>10} {:>10} {:>9.1}%",
|
|
"Enums",
|
|
stats.total_enums,
|
|
stats.documented_enums,
|
|
if stats.total_enums > 0 {
|
|
(stats.documented_enums as f64 / stats.total_enums as f64) * 100.0
|
|
} else {
|
|
0.0
|
|
}
|
|
);
|
|
println!(
|
|
"{:<25} {:>10} {:>10} {:>9.1}%",
|
|
"Traits",
|
|
stats.total_traits,
|
|
stats.documented_traits,
|
|
if stats.total_traits > 0 {
|
|
(stats.documented_traits as f64 / stats.total_traits as f64) * 100.0
|
|
} else {
|
|
0.0
|
|
}
|
|
);
|
|
println!(
|
|
"{:<25} {:>10} {:>10} {:>9.1}%",
|
|
"Type Aliases",
|
|
stats.total_type_aliases,
|
|
stats.documented_type_aliases,
|
|
if stats.total_type_aliases > 0 {
|
|
(stats.documented_type_aliases as f64 / stats.total_type_aliases as f64) * 100.0
|
|
} else {
|
|
0.0
|
|
}
|
|
);
|
|
println!(
|
|
"{:<25} {:>10} {:>10} {:>9.1}%",
|
|
"Constants",
|
|
stats.total_consts,
|
|
stats.documented_consts,
|
|
if stats.total_consts > 0 {
|
|
(stats.documented_consts as f64 / stats.total_consts as f64) * 100.0
|
|
} else {
|
|
0.0
|
|
}
|
|
);
|
|
println!("{}", "-" * 59);
|
|
println!(
|
|
"{:<25} {:>10} {:>10} {:>9.1}%",
|
|
"TOTAL",
|
|
stats.total_items(),
|
|
stats.documented_items(),
|
|
stats.coverage_pct()
|
|
);
|
|
|
|
println!("\nTarget: 80.0%");
|
|
if stats.coverage_pct() >= 80.0 {
|
|
println!("Status: PASS ✓");
|
|
} else {
|
|
println!("Status: FAIL - Need {:.1}% more", 80.0 - stats.coverage_pct());
|
|
}
|
|
}
|