use anyhow::Result;
use std::path::Path;
use crate::models::error::TemplateError;
use crate::services::complexity::{ComplexityMetrics, FileComplexityMetrics, FunctionComplexity};
use crate::services::context::{AstItem, FileContext};
use crate::services::file_classifier::FileClassifier;
use crate::ast::languages::python::PythonStrategy;
use crate::ast::languages::LanguageStrategy;
#[cfg(feature = "python-ast")]
use crate::services::enhanced_python_visitor::EnhancedPythonVisitor;
#[cfg(feature = "python-ast")]
use rustpython_parser::{ast::ModModule, Parse};
#[cfg(feature = "python-ast")]
fn parse_python_content(content: &str, path: &Path) -> Result<ModModule, TemplateError> {
let filename = path.display().to_string();
ModModule::parse(content, &filename)
.map_err(|e| TemplateError::InvalidUtf8(format!("Python parse error: {e}")))
}
pub async fn analyze_python_file_with_complexity(
path: &Path,
classifier: Option<&FileClassifier>,
) -> Result<FileComplexityMetrics, TemplateError> {
analyze_python_file_with_complexity_and_classifier(path, classifier).await
}
async fn analyze_python_file_with_complexity_and_classifier(
path: &Path,
_classifier: Option<&FileClassifier>,
) -> Result<FileComplexityMetrics, TemplateError> {
let content = tokio::fs::read_to_string(path)
.await
.map_err(TemplateError::Io)?;
let strategy = PythonStrategy::new();
let ast = strategy
.parse_file(path, &content)
.await
.map_err(|e| TemplateError::InvalidUtf8(e.to_string()))?;
let functions = strategy.extract_functions(&ast);
let mut function_metrics = Vec::new();
for (i, _node) in functions.iter().enumerate() {
function_metrics.push(FunctionComplexity {
name: format!("function_{i}"),
line_start: (i * 10) as u32,
line_end: ((i + 1) * 10) as u32,
metrics: ComplexityMetrics {
cyclomatic: 1, cognitive: 1, nesting_max: 0,
lines: 10,
halstead: None,
},
});
}
let (cyclomatic, cognitive) = strategy.calculate_complexity(&ast);
Ok(FileComplexityMetrics {
path: path.display().to_string(),
total_complexity: ComplexityMetrics {
cyclomatic: cyclomatic as u16,
cognitive: cognitive as u16,
nesting_max: 2,
lines: 100,
halstead: None,
},
functions: function_metrics,
classes: Vec::new(), })
}
pub async fn analyze_python_file(path: &Path) -> Result<FileContext, TemplateError> {
analyze_python_file_with_classifier(path, None).await
}
pub async fn analyze_python_file_with_classifier(
path: &Path,
_classifier: Option<&FileClassifier>,
) -> Result<FileContext, TemplateError> {
let content = tokio::fs::read_to_string(path)
.await
.map_err(TemplateError::Io)?;
#[cfg(feature = "python-ast")]
{
if let Ok(module) = parse_python_content(&content, path) {
let visitor = EnhancedPythonVisitor::new(path);
let items = visitor.extract_items(&module);
return Ok(FileContext {
path: path.display().to_string(),
language: "python".to_string(),
items,
complexity_metrics: None,
});
}
}
let strategy = PythonStrategy::new();
let ast = strategy
.parse_file(path, &content)
.await
.map_err(|e| TemplateError::InvalidUtf8(e.to_string()))?;
let functions = strategy.extract_functions(&ast);
let types = strategy.extract_types(&ast);
let _imports = strategy.extract_imports(&ast);
let mut items = Vec::new();
for (i, _node) in functions.iter().enumerate() {
items.push(AstItem::Function {
name: format!("function_{i}"),
visibility: String::new(), is_async: false, line: i * 10,
});
}
for (i, _node) in types.iter().enumerate() {
items.push(AstItem::Struct {
name: format!("class_{i}"),
visibility: String::new(),
fields_count: 0,
derives: vec![],
line: (functions.len() + i) * 10,
});
}
Ok(FileContext {
path: path.display().to_string(),
language: "python".to_string(),
items,
complexity_metrics: None,
})
}
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}