#!/usr/bin/env rust-script
use std::collections::HashMap;
use std::fs;
use std::path::Path;
use walkdir::WalkDir;
use regex::Regex;
#[derive(Debug, Clone)]
pub enum StabilityLevel {
Stable, Beta, Alpha, Experimental }
impl StabilityLevel {
pub fn emoji(&self) -> &'static str {
match self {
StabilityLevel::Stable => "🟢",
StabilityLevel::Beta => "🟡",
StabilityLevel::Alpha => "🟠",
StabilityLevel::Experimental => "🔴",
}
}
pub fn name(&self) -> &'static str {
match self {
StabilityLevel::Stable => "Stable",
StabilityLevel::Beta => "Beta",
StabilityLevel::Alpha => "Alpha",
StabilityLevel::Experimental => "Experimental",
}
}
}
#[derive(Debug)]
pub struct ModuleInfo {
pub name: String,
pub path: String,
pub version: String,
pub stability: StabilityLevel,
pub test_coverage: f64,
pub doc_coverage: f64,
pub api_count: usize,
pub has_examples: bool,
pub last_modified: String,
}
pub struct StabilityChecker {
modules: HashMap<String, ModuleInfo>,
}
impl StabilityChecker {
pub fn new() -> Self {
Self {
modules: HashMap::new(),
}
}
pub fn scan_modules(&mut self) -> Result<(), Box<dyn std::error::Error>> {
let service_path = "src/service";
if !Path::new(service_path).exists() {
eprintln!("❌ 服务目录不存在: {}", service_path);
return Ok(());
}
println!("🔍 扫描服务模块...");
for entry in WalkDir::new(service_path)
.into_iter()
.filter_map(|e| e.ok())
{
if entry.file_type().is_dir() {
let path = entry.path();
if let Some(module_info) = self.analyze_module_directory(path)? {
self.modules.insert(module_info.name.clone(), module_info);
}
}
}
Ok(())
}
fn analyze_module_directory(&self, path: &Path) -> Result<Option<ModuleInfo>, Box<dyn std::error::Error>> {
let path_str = path.to_string_lossy().to_string();
let version_re = Regex::new(r"/(v\d+)/")?;
let version = version_re
.captures(&path_str)
.and_then(|cap| cap.get(1))
.map(|m| m.as_str().to_string())
.unwrap_or_else(|| "v1".to_string());
let parts: Vec<&str> = path_str.split('/').collect();
if parts.len() < 3 {
return Ok(None);
}
let service_name = parts[2]; if service_name.is_empty() {
return Ok(None);
}
if !version_re.is_match(&path_str) {
return Ok(None);
}
let module_name = format!("{}::{}", service_name, version);
let api_count = self.count_apis_in_directory(path)?;
let doc_coverage = self.calculate_doc_coverage(path)?;
let has_examples = self.check_examples_exist(&service_name);
let stability = self.assess_stability(api_count, doc_coverage, has_examples, &version);
let test_coverage = self.estimate_test_coverage(&service_name, &version);
let module_info = ModuleInfo {
name: module_name,
path: path_str,
version,
stability,
test_coverage,
doc_coverage,
api_count,
has_examples,
last_modified: "2025-09-06".to_string(), };
Ok(Some(module_info))
}
fn count_apis_in_directory(&self, path: &Path) -> Result<usize, Box<dyn std::error::Error>> {
let mut api_count = 0;
for entry in WalkDir::new(path)
.into_iter()
.filter_map(|e| e.ok())
{
if entry.file_type().is_file()
&& entry.path().extension().map_or(false, |ext| ext == "rs") {
if let Ok(content) = fs::read_to_string(entry.path()) {
api_count += content.matches("pub fn ").count();
api_count += content.matches("pub async fn ").count();
}
}
}
Ok(api_count)
}
fn calculate_doc_coverage(&self, path: &Path) -> Result<f64, Box<dyn std::error::Error>> {
let mut total_items = 0;
let mut documented_items = 0;
for entry in WalkDir::new(path)
.into_iter()
.filter_map(|e| e.ok())
{
if entry.file_type().is_file()
&& entry.path().extension().map_or(false, |ext| ext == "rs") {
if let Ok(content) = fs::read_to_string(entry.path()) {
let lines: Vec<&str> = content.lines().collect();
for (i, line) in lines.iter().enumerate() {
if line.trim_start().starts_with("pub fn ") ||
line.trim_start().starts_with("pub async fn ") ||
line.trim_start().starts_with("pub struct ") ||
line.trim_start().starts_with("pub enum ") {
total_items += 1;
if i > 0 && lines[i-1].trim_start().starts_with("///") {
documented_items += 1;
}
}
}
}
}
}
if total_items == 0 {
Ok(0.0)
} else {
Ok((documented_items as f64 / total_items as f64) * 100.0)
}
}
fn check_examples_exist(&self, service_name: &str) -> bool {
let examples_dir = format!("examples/api/{}_", service_name);
Path::new(&examples_dir).exists() ||
Path::new(&format!("examples/api/{}.rs", service_name)).exists()
}
fn estimate_test_coverage(&self, service_name: &str, _version: &str) -> f64 {
match service_name {
"authentication" => 90.0, "im" => 85.0, "contact" => 60.0, "group" => 55.0, "cloud_docs" => 45.0, "attendance" => 40.0, _ => 20.0, }
}
fn assess_stability(&self, api_count: usize, doc_coverage: f64, has_examples: bool, version: &str) -> StabilityLevel {
let mut score = 0;
if api_count >= 20 { score += 2; }
else if api_count >= 10 { score += 1; }
if doc_coverage >= 80.0 { score += 2; }
else if doc_coverage >= 60.0 { score += 1; }
if has_examples { score += 1; }
match version {
"v1" => score += 2, "v2" => score += 1,
_ => {} }
match score {
7..=8 => StabilityLevel::Stable,
5..=6 => StabilityLevel::Beta,
3..=4 => StabilityLevel::Alpha,
_ => StabilityLevel::Experimental,
}
}
pub fn generate_report(&self) -> Result<(), Box<dyn std::error::Error>> {
println!("\n📊 API 稳定性和成熟度报告");
println!("=====================================\n");
let mut stable = Vec::new();
let mut beta = Vec::new();
let mut alpha = Vec::new();
let mut experimental = Vec::new();
for module in self.modules.values() {
match module.stability {
StabilityLevel::Stable => stable.push(module),
StabilityLevel::Beta => beta.push(module),
StabilityLevel::Alpha => alpha.push(module),
StabilityLevel::Experimental => experimental.push(module),
}
}
println!("📈 总体统计:");
println!(" 总模块数: {}", self.modules.len());
println!(" 🟢 稳定模块: {}", stable.len());
println!(" 🟡 测试模块: {}", beta.len());
println!(" 🟠 预览模块: {}", alpha.len());
println!(" 🔴 实验模块: {}", experimental.len());
println!();
self.print_modules("🟢 稳定模块 (生产可用)", &stable);
self.print_modules("🟡 测试模块 (功能完整)", &beta);
self.print_modules("🟠 预览模块 (开发中)", &alpha);
self.print_modules("🔴 实验模块 (概念验证)", &experimental);
self.generate_recommendations();
Ok(())
}
fn print_modules(&self, title: &str, modules: &[&ModuleInfo]) {
if !modules.is_empty() {
println!("{}", title);
println!("{}", "─".repeat(title.len()));
for module in modules {
println!(" {} {} ({})",
module.stability.emoji(),
module.name,
module.version
);
println!(" └─ API数量: {}, 文档覆盖: {:.1}%, 测试覆盖: {:.1}%, 示例: {}",
module.api_count,
module.doc_coverage,
module.test_coverage,
if module.has_examples { "✅" } else { "❌" }
);
}
println!();
}
}
fn generate_recommendations(&self) {
println!("💡 改进建议:");
println!("─────────────");
let mut high_priority = Vec::new();
let mut medium_priority = Vec::new();
for module in self.modules.values() {
match module.stability {
StabilityLevel::Experimental => {
if module.api_count >= 10 {
high_priority.push(format!("{}::{} - 添加文档和测试以提升至 Alpha",
module.name, module.version));
}
},
StabilityLevel::Alpha => {
if module.test_coverage < 50.0 {
high_priority.push(format!("{}::{} - 增加测试覆盖率至 60%+",
module.name, module.version));
}
if module.doc_coverage < 70.0 {
medium_priority.push(format!("{}::{} - 完善 API 文档",
module.name, module.version));
}
},
StabilityLevel::Beta => {
if module.test_coverage < 80.0 {
medium_priority.push(format!("{}::{} - 增加测试以达到 Stable 级别",
module.name, module.version));
}
},
StabilityLevel::Stable => {
}
}
}
if !high_priority.is_empty() {
println!(" 🔥 高优先级:");
for item in high_priority {
println!(" • {}", item);
}
}
if !medium_priority.is_empty() {
println!(" 📋 中优先级:");
for item in medium_priority {
println!(" • {}", item);
}
}
if high_priority.is_empty() && medium_priority.is_empty() {
println!(" ✨ 所有模块达到预期稳定性水平!");
}
}
pub fn save_report(&self, filename: &str) -> Result<(), Box<dyn std::error::Error>> {
let report_content = format!(
"# API 稳定性报告\n\n生成时间: {}\n\n总模块数: {}\n",
chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC"),
self.modules.len()
);
fs::write(filename, report_content)?;
println!("📄 报告已保存至: {}", filename);
Ok(())
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("🛠️ open-lark API 稳定性检查工具");
println!("===================================\n");
let mut checker = StabilityChecker::new();
checker.scan_modules()?;
checker.generate_report()?;
checker.save_report("reports/stability_report.md")?;
Ok(())
}