feat: smart integration detection with package classifier
- Add PackageClassifier with built-in dictionary (~200 popular packages) - Hardcode Python 3.10+ stdlib list to filter out standard library imports - Add PyPI API lookup for unknown packages (online mode, 3s timeout) - Cache PyPI results in .wtismycode/cache/pypi.json - Add --offline flag to skip PyPI lookups - Classify packages into: HTTP, Database, Queue, Storage, AI/ML, Auth, Testing, Logging, Internal, Third-party - User config integration_patterns override auto-detection - Update renderer to show integrations grouped by category - Update ARCHITECTURE.md template with new integration format
This commit is contained in:
@@ -12,6 +12,10 @@ pub fn load_config(config_path: &str) -> Result<Config> {
|
||||
}
|
||||
|
||||
pub fn analyze_project(root: &str, config: &Config) -> Result<ProjectModel> {
|
||||
analyze_project_with_options(root, config, false)
|
||||
}
|
||||
|
||||
pub fn analyze_project_with_options(root: &str, config: &Config, offline: bool) -> Result<ProjectModel> {
|
||||
println!("{}", "Scanning project...".cyan());
|
||||
|
||||
let scanner = FileScanner::new(config.clone());
|
||||
@@ -19,7 +23,7 @@ pub fn analyze_project(root: &str, config: &Config) -> Result<ProjectModel> {
|
||||
|
||||
println!(" Found {} Python files", python_files.len().to_string().yellow());
|
||||
|
||||
let analyzer = PythonAnalyzer::new(config.clone());
|
||||
let analyzer = PythonAnalyzer::new_with_options(config.clone(), offline);
|
||||
|
||||
let pb = ProgressBar::new(python_files.len() as u64);
|
||||
pb.set_style(ProgressStyle::default_bar()
|
||||
|
||||
@@ -37,6 +37,9 @@ enum Commands {
|
||||
/// Show what would be generated without writing files
|
||||
#[arg(long)]
|
||||
dry_run: bool,
|
||||
/// Skip PyPI API lookups, use only built-in dictionary
|
||||
#[arg(long)]
|
||||
offline: bool,
|
||||
},
|
||||
/// Check if documentation is up to date
|
||||
Check {
|
||||
@@ -61,9 +64,9 @@ fn main() -> Result<()> {
|
||||
Commands::Init { root, out } => {
|
||||
commands::init::init_project(root, out)?;
|
||||
}
|
||||
Commands::Generate { root, out, config, dry_run } => {
|
||||
Commands::Generate { root, out, config, dry_run, offline } => {
|
||||
let config = commands::generate::load_config(config)?;
|
||||
let model = commands::generate::analyze_project(root, &config)?;
|
||||
let model = commands::generate::analyze_project_with_options(root, &config, *offline)?;
|
||||
if *dry_run {
|
||||
commands::generate::dry_run_docs(&model, out, &config)?;
|
||||
} else {
|
||||
|
||||
@@ -19,17 +19,14 @@ pub fn print_generate_summary(model: &ProjectModel) {
|
||||
println!(" {} {}", "Edges:".bold(),
|
||||
model.edges.module_import_edges.len() + model.edges.symbol_call_edges.len());
|
||||
|
||||
let integrations: Vec<&str> = {
|
||||
let mut v = Vec::new();
|
||||
if model.symbols.values().any(|s| s.integrations_flags.http) { v.push("HTTP"); }
|
||||
if model.symbols.values().any(|s| s.integrations_flags.db) { v.push("DB"); }
|
||||
if model.symbols.values().any(|s| s.integrations_flags.queue) { v.push("Queue"); }
|
||||
if model.symbols.values().any(|s| s.integrations_flags.storage) { v.push("Storage"); }
|
||||
if model.symbols.values().any(|s| s.integrations_flags.ai) { v.push("AI/ML"); }
|
||||
v
|
||||
};
|
||||
if !integrations.is_empty() {
|
||||
println!(" {} {}", "Integrations:".bold(), integrations.join(", ").yellow());
|
||||
if !model.classified_integrations.is_empty() {
|
||||
let cats: Vec<String> = model.classified_integrations.iter()
|
||||
.filter(|(_, pkgs)| !pkgs.is_empty())
|
||||
.map(|(cat, pkgs)| format!("{} ({})", cat, pkgs.join(", ")))
|
||||
.collect();
|
||||
if !cats.is_empty() {
|
||||
println!(" {} {}", "Integrations:".bold(), cats.join(" | ").yellow());
|
||||
}
|
||||
}
|
||||
println!("{}", "─────────────────────────────────────".dimmed());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user