code-analyser 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,283 @@
1
+ Metadata-Version: 2.4
2
+ Name: code-analyser
3
+ Version: 0.1.0
4
+ Summary: Automated Code Analysis & Grading Assistant for Educators
5
+ Project-URL: Homepage, https://github.com/yourusername/codelens
6
+ Project-URL: Repository, https://github.com/yourusername/codelens.git
7
+ Author: CodeLens Team
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Education
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Requires-Python: >=3.10
16
+ Requires-Dist: aiosqlite>=0.19.0
17
+ Requires-Dist: alembic>=1.13.0
18
+ Requires-Dist: docker>=6.1.0
19
+ Requires-Dist: fastapi>=0.104.0
20
+ Requires-Dist: httpx>=0.25.0
21
+ Requires-Dist: mypy>=1.7.0
22
+ Requires-Dist: passlib[bcrypt]>=1.7.4
23
+ Requires-Dist: pydantic-settings>=2.1.0
24
+ Requires-Dist: pydantic>=2.5.0
25
+ Requires-Dist: python-jose[cryptography]>=3.3.0
26
+ Requires-Dist: python-multipart>=0.0.6
27
+ Requires-Dist: ruff>=0.1.0
28
+ Requires-Dist: sqlalchemy>=2.0.0
29
+ Requires-Dist: structlog>=23.2.0
30
+ Requires-Dist: uvicorn[standard]>=0.24.0
31
+ Provides-Extra: dev
32
+ Requires-Dist: black>=23.11.0; extra == 'dev'
33
+ Requires-Dist: isort>=5.12.0; extra == 'dev'
34
+ Requires-Dist: pre-commit>=3.5.0; extra == 'dev'
35
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
36
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
37
+ Requires-Dist: pytest>=7.4.0; extra == 'dev'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # CodeLens
41
+
42
+ <!-- BADGES:START -->
43
+ [![edtech](https://img.shields.io/badge/-edtech-4caf50?style=flat-square)](https://github.com/topics/edtech) [![automated-grading](https://img.shields.io/badge/-automated--grading-blue?style=flat-square)](https://github.com/topics/automated-grading) [![code-analysis](https://img.shields.io/badge/-code--analysis-blue?style=flat-square)](https://github.com/topics/code-analysis) [![docker](https://img.shields.io/badge/-docker-2496ed?style=flat-square)](https://github.com/topics/docker) [![education](https://img.shields.io/badge/-education-blue?style=flat-square)](https://github.com/topics/education) [![fastapi](https://img.shields.io/badge/-fastapi-009688?style=flat-square)](https://github.com/topics/fastapi) [![microservice](https://img.shields.io/badge/-microservice-blue?style=flat-square)](https://github.com/topics/microservice) [![plagiarism-detection](https://img.shields.io/badge/-plagiarism--detection-blue?style=flat-square)](https://github.com/topics/plagiarism-detection) [![python](https://img.shields.io/badge/-python-3776ab?style=flat-square)](https://github.com/topics/python) [![static-analysis](https://img.shields.io/badge/-static--analysis-blue?style=flat-square)](https://github.com/topics/static-analysis)
44
+ <!-- BADGES:END -->
45
+
46
+ **Automated Code Analysis & Grading Assistant for Educators**
47
+
48
+ CodeLens is a comprehensive microservice designed to help educators analyze, validate, and grade student code submissions across multiple programming languages. It provides automated analysis, plagiarism detection, sandboxed code execution, and detailed feedback generation.
49
+
50
+ ## 🚀 Features
51
+
52
+ ### Core Analysis
53
+ - **Static Code Analysis**: Syntax validation, style checking, complexity metrics
54
+ - **Configurable Tools**: Support for ruff, mypy, and other analysis tools
55
+ - **Multi-language Support**: Currently supports Python with extensible architecture
56
+ - **Quality Metrics**: Lines of code, cyclomatic complexity, maintainability index
57
+
58
+ ### Code Execution
59
+ - **Secure Sandboxing**: Docker-based isolated execution environment
60
+ - **Test Execution**: Support for pytest and unittest frameworks
61
+ - **Resource Limits**: CPU, memory, and time constraints for safety
62
+ - **Input/Output Validation**: Compare expected vs actual outputs
63
+
64
+ ### Plagiarism Detection
65
+ - **Multiple Methods**: AST structural, token-based, line-based similarity
66
+ - **Cross-submission Comparison**: Compare against other student submissions
67
+ - **Configurable Thresholds**: Adjustable similarity detection sensitivity
68
+ - **Review System**: Manual review and flagging of potential plagiarism
69
+
70
+ ### Batch Processing
71
+ - **Directory Processing**: Analyze entire folders of submissions
72
+ - **Parallel Processing**: Concurrent analysis for improved performance
73
+ - **Student Info Extraction**: Automatic extraction of student IDs from filenames
74
+ - **CLI Interface**: Command-line tools for instructors
75
+
76
+ ### Grading & Feedback
77
+ - **Rubric-based Grading**: Configurable grading criteria and weights
78
+ - **Automated Scoring**: Calculate grades based on multiple factors
79
+ - **Detailed Feedback**: Constructive comments and improvement suggestions
80
+ - **Progress Tracking**: Track student performance over time
81
+
82
+ ## 🏗️ Architecture
83
+
84
+ ```
85
+ codelens/
86
+ ├── api/ # FastAPI routes and schemas
87
+ ├── analyzers/ # Code analysis engines
88
+ ├── services/ # Business logic services
89
+ ├── models/ # Database models
90
+ ├── db/ # Database configuration
91
+ ├── core/ # Configuration and settings
92
+ └── utils/ # Utility functions
93
+ ```
94
+
95
+ ## 🛠️ Installation
96
+
97
+ 1. **Clone the repository**
98
+ ```bash
99
+ git clone <repository-url>
100
+ cd code-lens
101
+ ```
102
+
103
+ 2. **Install dependencies**
104
+ ```bash
105
+ pip install -e .
106
+ ```
107
+
108
+ 3. **Install analysis tools**
109
+ ```bash
110
+ pip install ruff mypy pytest
111
+ ```
112
+
113
+ 4. **Setup Docker** (for code execution)
114
+ ```bash
115
+ docker pull python:3.11-slim
116
+ ```
117
+
118
+ 5. **Initialize database**
119
+ ```bash
120
+ python -m codelens.main
121
+ ```
122
+
123
+ ## 🚀 Quick Start
124
+
125
+ ### Web API Server
126
+
127
+ Start the FastAPI server:
128
+
129
+ ```bash
130
+ # Development mode
131
+ uvicorn codelens.main:app --reload
132
+
133
+ # Production mode
134
+ uvicorn codelens.main:app --host 0.0.0.0 --port 8000
135
+ ```
136
+
137
+ The API will be available at `http://localhost:8003` with documentation at `/docs`.
138
+
139
+ ### CLI Usage
140
+
141
+ Analyze a single file:
142
+ ```bash
143
+ python -m codelens analyze submission.py --student-id cs123456
144
+ ```
145
+
146
+ Process a directory of submissions:
147
+ ```bash
148
+ python -m codelens batch /path/to/submissions --language python --detailed
149
+ ```
150
+
151
+ Generate batch report:
152
+ ```bash
153
+ python -m codelens batch /submissions --output results.json --rubric-id 1
154
+ ```
155
+
156
+ ## 📡 API Examples
157
+
158
+ ### Analyze Python Code
159
+ ```bash
160
+ curl -X POST "http://localhost:8003/api/v1/analyze/python" \
161
+ -H "Content-Type: application/json" \
162
+ -d '{
163
+ "code": "def hello():\n print(\"Hello, World!\")",
164
+ "language": "python",
165
+ "student_id": "cs123456",
166
+ "check_similarity": true,
167
+ "run_tests": false
168
+ }'
169
+ ```
170
+
171
+ ### Batch Analysis
172
+ ```bash
173
+ curl -X POST "http://localhost:8003/api/v1/analyze/batch" \
174
+ -H "Content-Type: application/json" \
175
+ -d '{
176
+ "files": [
177
+ {"code": "def add(a, b): return a + b", "path": "student1.py"},
178
+ {"code": "def multiply(x, y): return x * y", "path": "student2.py"}
179
+ ],
180
+ "language": "python",
181
+ "check_similarity": true
182
+ }'
183
+ ```
184
+
185
+ ### Create Rubric
186
+ ```bash
187
+ curl -X POST "http://localhost:8003/api/v1/rubrics/" \
188
+ -H "Content-Type: application/json" \
189
+ -d '{
190
+ "name": "Python Assignment 1",
191
+ "language": "python",
192
+ "criteria": {"functionality": 40, "style": 30, "documentation": 30},
193
+ "weights": {"functionality": 0.4, "style": 0.3, "documentation": 0.3},
194
+ "total_points": 100
195
+ }'
196
+ ```
197
+
198
+ ## ⚙️ Configuration
199
+
200
+ Configuration is managed through environment variables and the `codelens/core/config.py` file:
201
+
202
+ ```python
203
+ # Analysis tools
204
+ RUFF_ENABLED=true
205
+ MYPY_ENABLED=true
206
+ MAX_LINE_LENGTH=88
207
+
208
+ # Execution limits
209
+ EXECUTION_TIMEOUT=30
210
+ MEMORY_LIMIT=128m
211
+ CPU_LIMIT=0.5
212
+
213
+ # Similarity detection
214
+ SIMILARITY_ENABLED=true
215
+ SIMILARITY_THRESHOLD=0.8
216
+
217
+ # Database
218
+ DATABASE_URL=sqlite+aiosqlite:///./codelens.db
219
+ ```
220
+
221
+ ## 🔒 Security
222
+
223
+ - **Sandboxed Execution**: All code runs in isolated Docker containers
224
+ - **Resource Limits**: CPU, memory, and time constraints prevent abuse
225
+ - **No Network Access**: Containers run without network connectivity
226
+ - **Input Validation**: All inputs are validated and sanitized
227
+ - **Code Analysis Only**: Original submissions are not stored permanently
228
+
229
+ ## 📊 Supported Analysis
230
+
231
+ ### Python
232
+ - **Linting**: ruff (configurable rules)
233
+ - **Type Checking**: mypy (optional)
234
+ - **Metrics**: Complexity, maintainability, documentation coverage
235
+ - **Testing**: pytest, unittest support
236
+ - **Similarity**: AST-based structural comparison
237
+
238
+ ### Future Languages
239
+ The architecture supports extension to JavaScript, HTML/CSS, Java, and other languages.
240
+
241
+ ## 🤝 Educational Use Cases
242
+
243
+ - **Introductory Programming Courses**: Automated grading for basic assignments
244
+ - **Code Quality Assessment**: Teaching best practices and style guidelines
245
+ - **Plagiarism Detection**: Identifying potential academic dishonesty
246
+ - **Progress Tracking**: Monitoring student improvement over time
247
+ - **Immediate Feedback**: Helping students learn from mistakes
248
+
249
+ ## 📈 Example Workflow
250
+
251
+ 1. **Instructor**: Creates assignment with rubric
252
+ 2. **Students**: Submit code files (via LMS or direct upload)
253
+ 3. **CodeLens**: Analyzes submissions automatically
254
+ 4. **System**: Generates grades and detailed feedback
255
+ 5. **Instructor**: Reviews flagged similarities and edge cases
256
+ 6. **Students**: Receive feedback and suggestions for improvement
257
+
258
+ ## 🧪 Testing
259
+
260
+ Run the test suite:
261
+ ```bash
262
+ pytest tests/
263
+ ```
264
+
265
+ Run with coverage:
266
+ ```bash
267
+ pytest --cov=codelens tests/
268
+ ```
269
+
270
+ ## 📝 License
271
+
272
+ This project is licensed under the MIT License - see the LICENSE file for details.
273
+
274
+ ## 🙏 Acknowledgments
275
+
276
+ - Built with FastAPI for high-performance web APIs
277
+ - Uses Docker for secure code execution
278
+ - Powered by ruff and mypy for Python analysis
279
+ - Inspired by the need for fair and consistent code grading
280
+
281
+ ---
282
+
283
+ **CodeLens**: Empowering educators with intelligent code analysis 🔍✨
@@ -0,0 +1,34 @@
1
+ codelens/__init__.py,sha256=sx1HI1jOpPOML0ACWaCe1Hp1Kdu2K6lswGk9zRDhoV8,207
2
+ codelens/__main__.py,sha256=4_KQpQu_5Z0VSvejCIvAGjnMEVgo8fMpaWuRYHAWVYE,431
3
+ codelens/cli.py,sha256=w5haGGK7xaril4zcxdCIzks54GDcxRBn5SwEbVqd8Bg,11799
4
+ codelens/main.py,sha256=rdjDKtbCMWZmq87KkznFxrmt1LVcHs8lTbbMc1ZC6kw,2999
5
+ codelens/analyzers/__init__.py,sha256=rbtiHE0yEmuf0OxHTBoxfeuGVNXWNiCV5IlBYYcvRbg,749
6
+ codelens/analyzers/base.py,sha256=f0dglJCP-bYKhQjJlh5nL8qM5S_X0Xl8L8ir1oQnWAM,4259
7
+ codelens/analyzers/manager.py,sha256=UaNO7o7UZh1AtT9gKBSuqqtSBWBInDSwzJAJB5NDmGs,7043
8
+ codelens/analyzers/python_analyzer.py,sha256=SyES-FlJKvkFxryztcQUkVM7DPT90O9ff2nREZFCRFk,13368
9
+ codelens/analyzers/similarity_analyzer.py,sha256=sp-sFpheTeRrARS-S7wGj5EX9pWEzm8E37poZDJ-9I8,18864
10
+ codelens/api/__init__.py,sha256=Mow0Ffn6nVs3QDEXSEZHOMWwKmSBMeSxduYjJt9CjkA,31
11
+ codelens/api/schemas.py,sha256=Dd9xNP5T5p4eiJpBe3byLO0yBRLJnOF32jDCZaDqsL4,10939
12
+ codelens/api/routes/__init__.py,sha256=JXHLVi48XM5_zVjoBSyHvUWqT4XDhkHP9X_cjMLbk8c,24
13
+ codelens/api/routes/analysis.py,sha256=NEmYUoRHth_-wOnHKddzjT6-6EdaKPSjvuG0oglPUIc,17035
14
+ codelens/api/routes/reports.py,sha256=Vd-7S3PfeXXlB-X0MPtZEEK6FnlGb9Y_0LqS9U2JsPE,14539
15
+ codelens/api/routes/rubrics.py,sha256=rhFE9c547xhLk93Ip-XaypU4PZcuOkYcBsuXgWHvNLg,11562
16
+ codelens/core/__init__.py,sha256=Xls_Bpc6QdAjMS_pbplPEzHjHuAtjtwaK0Dld0iTD-A,34
17
+ codelens/core/config.py,sha256=-NNVDeimo3yPhLHU9M8pOiAIb8Lw0R2ECWETMq0R7Jg,2348
18
+ codelens/db/__init__.py,sha256=20e81aCej9FuAojE6v5ATpfrueelBUXRXlvFIY6IYo0,44
19
+ codelens/db/database.py,sha256=9f4zQDdemrIJgNgzhiUC-7lnsck4kq34SkDQr58-fU8,1440
20
+ codelens/models/__init__.py,sha256=QtfavOkLD6nvBF8ggCG2KBSvwujDmlDAWXJ9vM7L7sw,298
21
+ codelens/models/assignments.py,sha256=2OzS45_C9jrDgtfeSvvLbOa71wfQ76SBBNM560ivNjY,3999
22
+ codelens/models/reports.py,sha256=cIff31nGdmB4kJy-udEsLRRFHEFZ_EcyX0ZOFW3O8dI,6266
23
+ codelens/models/rubrics.py,sha256=y4HrPyYb5AA7Dnvf2FV7q6a5DQHL3QKluJzqAj7xVuw,2826
24
+ codelens/services/__init__.py,sha256=W5iKEV_YDcKrznlOLeqRj0CAiT5HdBhhjQoHrv8l4kc,859
25
+ codelens/services/batch_processor.py,sha256=TcSUkacWg1Qz1J7W3w0f31kyggSucMzl6KUxDxngPyc,17739
26
+ codelens/services/code_executor.py,sha256=bC2pqColCTeB7jcUbPTK5_mjah0TWbwXoLjY0EFCJvU,10862
27
+ codelens/services/sandbox.py,sha256=Oho5E43M1nYYUHh70wKuF9dLlMwFqTvcmI0Kp29Qj98,14059
28
+ codelens/services/similarity_service.py,sha256=vZu9OSCf6e1efp3NUmouYjCT1I3Rl-tMysI_iL11ekM,16549
29
+ codelens/utils/__init__.py,sha256=-rjYOnL-A4W9vUf00lclhb09GGYxLJRrnveveBdyCok,714
30
+ codelens/utils/helpers.py,sha256=xguLk0S9RFm1TcyskFYsYzeIFfTq6Km8I2EQZFyKtXY,5961
31
+ code_analyser-0.1.0.dist-info/METADATA,sha256=TFP0d0nnJTNwM5A0QMfp9mXlTutNRm42SHyNlqWHPO4,9661
32
+ code_analyser-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
33
+ code_analyser-0.1.0.dist-info/licenses/LICENSE,sha256=tP28nznO05HKBQN8uZ9NSvmcpzCWMj4P8e6F7IDj24Q,1070
34
+ code_analyser-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Michael Borck
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
codelens/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ """
2
+ CodeLens - Automated Code Analysis & Grading Assistant for Educators
3
+ """
4
+
5
+ __version__ = "0.1.0"
6
+ __author__ = "CodeLens Team"
7
+ __description__ = "Automated Code Analysis & Grading Assistant for Educators"
codelens/__main__.py ADDED
@@ -0,0 +1,19 @@
1
+ """
2
+ Entry point for running CodeLens as a module
3
+ """
4
+
5
+ import asyncio
6
+ import sys
7
+
8
+ from codelens.cli import main
9
+
10
+ if __name__ == "__main__":
11
+ try:
12
+ exit_code = asyncio.run(main())
13
+ sys.exit(exit_code)
14
+ except KeyboardInterrupt:
15
+ print("\nInterrupted by user", file=sys.stderr)
16
+ sys.exit(1)
17
+ except Exception as e:
18
+ print(f"Unexpected error: {str(e)}", file=sys.stderr)
19
+ sys.exit(1)
@@ -0,0 +1,30 @@
1
+ """Code analyzers for different languages"""
2
+
3
+ from .base import AnalysisIssue, AnalysisResult, BaseAnalyzer, CodeMetrics, Severity
4
+ from .manager import AnalyzerManager, analyzer_manager
5
+ from .python_analyzer import PythonAnalyzer
6
+ from .similarity_analyzer import (
7
+ PythonSimilarityAnalyzer,
8
+ SimilarityDetector,
9
+ SimilarityMatch,
10
+ SimilarityMethod,
11
+ SimilarityResult,
12
+ similarity_detector,
13
+ )
14
+
15
+ __all__ = [
16
+ "BaseAnalyzer",
17
+ "AnalysisResult",
18
+ "AnalysisIssue",
19
+ "CodeMetrics",
20
+ "Severity",
21
+ "PythonAnalyzer",
22
+ "AnalyzerManager",
23
+ "analyzer_manager",
24
+ "SimilarityMethod",
25
+ "SimilarityMatch",
26
+ "SimilarityResult",
27
+ "PythonSimilarityAnalyzer",
28
+ "SimilarityDetector",
29
+ "similarity_detector",
30
+ ]
@@ -0,0 +1,139 @@
1
+ """
2
+ Base analyzer interface and common functionality
3
+ """
4
+
5
+ import ast
6
+ import hashlib
7
+ from abc import ABC, abstractmethod
8
+ from dataclasses import dataclass
9
+ from enum import Enum
10
+ from typing import Any
11
+
12
+
13
+ class Severity(Enum):
14
+ """Issue severity levels"""
15
+ INFO = "info"
16
+ WARNING = "warning"
17
+ ERROR = "error"
18
+ CRITICAL = "critical"
19
+
20
+
21
+ @dataclass
22
+ class AnalysisIssue:
23
+ """Represents an issue found during analysis"""
24
+ line: int
25
+ column: int = 0
26
+ severity: Severity = Severity.WARNING
27
+ code: str = "" # Error/warning code (e.g., "E501", "W292")
28
+ message: str = ""
29
+ category: str = "general" # style, syntax, logic, complexity, etc.
30
+ suggestion: str | None = None # How to fix the issue
31
+
32
+
33
+ @dataclass
34
+ class CodeMetrics:
35
+ """Code quality metrics"""
36
+ lines_of_code: int = 0
37
+ lines_of_comments: int = 0
38
+ blank_lines: int = 0
39
+ cyclomatic_complexity: int = 0
40
+ cognitive_complexity: int = 0
41
+ function_count: int = 0
42
+ class_count: int = 0
43
+ max_nesting_depth: int = 0
44
+ maintainability_index: float = 0.0
45
+
46
+
47
+ @dataclass
48
+ class AnalysisResult:
49
+ """Results from code analysis"""
50
+ success: bool
51
+ issues: list[AnalysisIssue]
52
+ metrics: CodeMetrics
53
+ execution_time: float = 0.0
54
+ analyzer_version: str = ""
55
+ raw_output: str | None = None # Raw analyzer output for debugging
56
+
57
+
58
+ class BaseAnalyzer(ABC):
59
+ """Base class for all code analyzers"""
60
+
61
+ def __init__(self, config: dict[str, Any] | None = None):
62
+ self.config = config or {}
63
+ self.name = self.__class__.__name__
64
+
65
+ @abstractmethod
66
+ async def analyze(self, code: str, file_path: str = "temp.py") -> AnalysisResult:
67
+ """
68
+ Analyze the given code and return results
69
+
70
+ Args:
71
+ code: Source code to analyze
72
+ file_path: Optional file path for context
73
+
74
+ Returns:
75
+ AnalysisResult with issues and metrics
76
+ """
77
+ pass
78
+
79
+ @abstractmethod
80
+ def get_version(self) -> str:
81
+ """Get the version of the analyzer tool"""
82
+ pass
83
+
84
+ def get_code_hash(self, code: str) -> str:
85
+ """Generate SHA-256 hash of code for caching/identification"""
86
+ return hashlib.sha256(code.encode('utf-8')).hexdigest()
87
+
88
+ def parse_ast(self, code: str) -> ast.AST | None:
89
+ """Parse code into AST, return None if syntax error"""
90
+ try:
91
+ return ast.parse(code)
92
+ except SyntaxError:
93
+ return None
94
+
95
+ def calculate_basic_metrics(self, code: str) -> CodeMetrics:
96
+ """Calculate basic code metrics from source"""
97
+ lines = code.split('\n')
98
+
99
+ metrics = CodeMetrics()
100
+ metrics.lines_of_code = len([line for line in lines if line.strip()])
101
+ metrics.blank_lines = len([line for line in lines if not line.strip()])
102
+ metrics.lines_of_comments = len([
103
+ line for line in lines
104
+ if line.strip().startswith('#') and not line.strip().startswith('#!')
105
+ ])
106
+
107
+ # Try to get AST metrics
108
+ try:
109
+ tree = ast.parse(code)
110
+ metrics.function_count = len([
111
+ node for node in ast.walk(tree)
112
+ if isinstance(node, ast.FunctionDef)
113
+ ])
114
+ metrics.class_count = len([
115
+ node for node in ast.walk(tree)
116
+ if isinstance(node, ast.ClassDef)
117
+ ])
118
+ metrics.max_nesting_depth = self._calculate_nesting_depth(tree)
119
+ except SyntaxError:
120
+ pass # Keep default values if code doesn't parse
121
+
122
+ return metrics
123
+
124
+ def _calculate_nesting_depth(self, node: ast.AST, current_depth: int = 0) -> int:
125
+ """Calculate maximum nesting depth in AST"""
126
+ max_depth = current_depth
127
+
128
+ # Nodes that increase nesting depth
129
+ nesting_nodes = (ast.If, ast.While, ast.For, ast.With, ast.Try)
130
+
131
+ for child in ast.iter_child_nodes(node):
132
+ if isinstance(child, nesting_nodes):
133
+ child_depth = self._calculate_nesting_depth(child, current_depth + 1)
134
+ max_depth = max(max_depth, child_depth)
135
+ else:
136
+ child_depth = self._calculate_nesting_depth(child, current_depth)
137
+ max_depth = max(max_depth, child_depth)
138
+
139
+ return max_depth