codemap-python 0.1.3__tar.gz → 0.1.4__tar.gz
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.
- {codemap_python-0.1.3 → codemap_python-0.1.4}/PKG-INFO +24 -12
- {codemap_python-0.1.3 → codemap_python-0.1.4}/README.md +22 -9
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/call_graph/call_extractor.py +7 -11
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/core/ast_parser.py +7 -13
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/core/import_extractor.py +6 -10
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/explain/explain_runner.py +6 -11
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/runners/phase4_runner.py +6 -11
- codemap_python-0.1.4/analysis/utils/bom_handler.py +119 -0
- codemap_python-0.1.4/analysis/utils/progress_spinner.py +85 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/cli.py +7 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/codemap_python.egg-info/PKG-INFO +24 -12
- {codemap_python-0.1.3 → codemap_python-0.1.4}/codemap_python.egg-info/SOURCES.txt +2 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/pyproject.toml +2 -3
- codemap_python-0.1.4/tests/test_cli_invalid_escape_warnings.py +35 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/ui/app.py +5 -1
- {codemap_python-0.1.3 → codemap_python-0.1.4}/ui/static/app.js +63 -4
- codemap_python-0.1.3/analysis/utils/bom_handler.py +0 -55
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/__init__.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/architecture/__init__.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/architecture/architecture_engine.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/architecture/dependency_cycles.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/architecture/risk_radar.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/call_graph/__init__.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/call_graph/call_graph_builder.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/call_graph/call_resolver.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/call_graph/context_models.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/call_graph/cross_file_resolver.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/call_graph/execution_tracker.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/call_graph/flow_builder.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/call_graph/models.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/core/__init__.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/core/ast_context.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/core/class_extractor.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/core/function_extractor.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/explain/__init__.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/explain/docstring_extractor.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/explain/repo_summary_generator.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/explain/return_analyzer.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/explain/risk_flags.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/explain/signature_extractor.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/explain/summary_generator.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/graph/__init__.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/graph/callgraph_index.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/graph/entrypoint_detector.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/graph/impact_analyzer.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/indexing/__init__.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/indexing/import_resolver.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/indexing/symbol_index.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/runners/__init__.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/utils/__init__.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/utils/ast_helpers.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/utils/cache_manager.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/utils/path_resolver.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/analysis/utils/repo_fetcher.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/codemap_cli.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/codemap_python.egg-info/dependency_links.txt +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/codemap_python.egg-info/entry_points.txt +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/codemap_python.egg-info/requires.txt +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/codemap_python.egg-info/top_level.txt +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/security_utils.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/setup.cfg +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/tests/test_cache_cli_commands.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/tests/test_cache_retention.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/tests/test_no_key_persistence.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/tests/test_registry_session_mode.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/tests/test_security_cli_integration.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/tests/test_security_redaction.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/tests/test_symbol_explain_cache.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/tests/test_symbol_info_endpoint.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/tests/test_ui_private_mode_security.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/tests/test_ui_retention_controls.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/ui/__init__.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/ui/device_id.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/ui/static/styles.css +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/ui/templates/index.html +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/ui/utils/__init__.py +0 -0
- {codemap_python-0.1.3 → codemap_python-0.1.4}/ui/utils/registry_manager.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codemap-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Local Python code analysis tool - understand architecture, dependencies, and call graphs
|
|
5
5
|
Author-email: ADITYA <aditykushwaha69@gmail.com>
|
|
6
|
-
License: MIT
|
|
6
|
+
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/ADITYA-kus/codemap_ai
|
|
8
8
|
Project-URL: Repository, https://github.com/ADITYA-kus/codemap_ai.git
|
|
9
9
|
Project-URL: Issues, https://github.com/ADITYA-kus/codemap_ai/issues
|
|
@@ -11,7 +11,6 @@ Project-URL: Documentation, https://github.com/ADITYA-kus/codemap_ai#readme
|
|
|
11
11
|
Keywords: code-analysis,python,architecture,call-graph,cli,dashboard,local,privacy
|
|
12
12
|
Classifier: Development Status :: 4 - Beta
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
15
14
|
Classifier: Programming Language :: Python :: 3
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -159,22 +158,35 @@ codemap open --port 8000
|
|
|
159
158
|
|
|
160
159
|
### Cache Management
|
|
161
160
|
```bash
|
|
162
|
-
# List all analyzed repositories
|
|
161
|
+
# 📋 List all analyzed repositories and their cache info
|
|
163
162
|
codemap cache list
|
|
164
163
|
|
|
165
|
-
# Show cache
|
|
166
|
-
codemap cache info <
|
|
164
|
+
# 📊 Show detailed cache information for a specific repository
|
|
165
|
+
codemap cache info --path <repo_directory>
|
|
167
166
|
|
|
168
|
-
#
|
|
169
|
-
codemap cache
|
|
167
|
+
# ⏱️ Set cache retention policy (automatically clean old caches)
|
|
168
|
+
codemap cache retention --path <repo_directory> --days 30 --yes
|
|
170
169
|
|
|
171
|
-
#
|
|
172
|
-
codemap cache
|
|
170
|
+
# 🧹 Preview what would be cleaned (safe, no deletion)
|
|
171
|
+
codemap cache sweep --dry-run
|
|
173
172
|
|
|
174
|
-
#
|
|
175
|
-
codemap cache sweep
|
|
173
|
+
# 🧹 Actually clean up expired caches (requires --yes confirmation)
|
|
174
|
+
codemap cache sweep --yes
|
|
175
|
+
|
|
176
|
+
# 🗑️ Clear cache for a specific repository (preview first)
|
|
177
|
+
codemap cache clear --path <repo_directory> --dry-run
|
|
178
|
+
|
|
179
|
+
# 🗑️ Actually delete a repository's cache (requires --yes confirmation)
|
|
180
|
+
codemap cache clear --path <repo_directory> --yes
|
|
176
181
|
```
|
|
177
182
|
|
|
183
|
+
**Cache Management Tips:**
|
|
184
|
+
- ✅ Always use `--dry-run` first to preview changes
|
|
185
|
+
- ✅ Add `--yes` flag to skip confirmation (useful in scripts)
|
|
186
|
+
- ✅ Default retention is 14 days; adjust with `--days <number>`
|
|
187
|
+
- ✅ Cache is stored in: `~/.codemap_cache/` (varies by OS)
|
|
188
|
+
- ✅ Use `cache list` to see all cached repositories and their sizes
|
|
189
|
+
|
|
178
190
|
**Get GitHub Token (for private repos):**
|
|
179
191
|
1. Go to https://github.com/settings/tokens
|
|
180
192
|
2. Click "Generate new token" → "Generate new token (classic)"
|
|
@@ -131,22 +131,35 @@ codemap open --port 8000
|
|
|
131
131
|
|
|
132
132
|
### Cache Management
|
|
133
133
|
```bash
|
|
134
|
-
# List all analyzed repositories
|
|
134
|
+
# 📋 List all analyzed repositories and their cache info
|
|
135
135
|
codemap cache list
|
|
136
136
|
|
|
137
|
-
# Show cache
|
|
138
|
-
codemap cache info <
|
|
137
|
+
# 📊 Show detailed cache information for a specific repository
|
|
138
|
+
codemap cache info --path <repo_directory>
|
|
139
139
|
|
|
140
|
-
#
|
|
141
|
-
codemap cache
|
|
140
|
+
# ⏱️ Set cache retention policy (automatically clean old caches)
|
|
141
|
+
codemap cache retention --path <repo_directory> --days 30 --yes
|
|
142
142
|
|
|
143
|
-
#
|
|
144
|
-
codemap cache
|
|
143
|
+
# 🧹 Preview what would be cleaned (safe, no deletion)
|
|
144
|
+
codemap cache sweep --dry-run
|
|
145
145
|
|
|
146
|
-
#
|
|
147
|
-
codemap cache sweep
|
|
146
|
+
# 🧹 Actually clean up expired caches (requires --yes confirmation)
|
|
147
|
+
codemap cache sweep --yes
|
|
148
|
+
|
|
149
|
+
# 🗑️ Clear cache for a specific repository (preview first)
|
|
150
|
+
codemap cache clear --path <repo_directory> --dry-run
|
|
151
|
+
|
|
152
|
+
# 🗑️ Actually delete a repository's cache (requires --yes confirmation)
|
|
153
|
+
codemap cache clear --path <repo_directory> --yes
|
|
148
154
|
```
|
|
149
155
|
|
|
156
|
+
**Cache Management Tips:**
|
|
157
|
+
- ✅ Always use `--dry-run` first to preview changes
|
|
158
|
+
- ✅ Add `--yes` flag to skip confirmation (useful in scripts)
|
|
159
|
+
- ✅ Default retention is 14 days; adjust with `--days <number>`
|
|
160
|
+
- ✅ Cache is stored in: `~/.codemap_cache/` (varies by OS)
|
|
161
|
+
- ✅ Use `cache list` to see all cached repositories and their sizes
|
|
162
|
+
|
|
150
163
|
**Get GitHub Token (for private repos):**
|
|
151
164
|
1. Go to https://github.com/settings/tokens
|
|
152
165
|
2. Click "Generate new token" → "Generate new token (classic)"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
# AST Call detection
|
|
2
|
-
|
|
3
|
-
import ast
|
|
4
|
-
from analysis.utils.bom_handler import
|
|
1
|
+
# AST Call detection
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from analysis.utils.bom_handler import read_source_file, parse_source_to_ast
|
|
5
5
|
|
|
6
6
|
class FunctionCallVisitor(ast.NodeVisitor):
|
|
7
7
|
def __init__(self, file_path):
|
|
@@ -82,13 +82,9 @@ class FunctionCallVisitor(ast.NodeVisitor):
|
|
|
82
82
|
return None
|
|
83
83
|
|
|
84
84
|
|
|
85
|
-
def extract_function_calls(file_path):
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
# Remove BOM if present
|
|
90
|
-
source = remove_bom(source)
|
|
91
|
-
tree = ast.parse(source)
|
|
85
|
+
def extract_function_calls(file_path):
|
|
86
|
+
source = read_source_file(file_path)
|
|
87
|
+
tree = parse_source_to_ast(source, file_path=file_path)
|
|
92
88
|
|
|
93
89
|
visitor = FunctionCallVisitor(file_path)
|
|
94
90
|
visitor.visit(tree)
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
# AST Parser Module
|
|
2
|
-
import
|
|
3
|
-
from analysis.utils.bom_handler import remove_bom
|
|
1
|
+
# AST Parser Module
|
|
2
|
+
from analysis.utils.bom_handler import read_and_parse_python_file
|
|
4
3
|
|
|
5
4
|
|
|
6
|
-
def parse_python_file(file_path):
|
|
7
|
-
"""Parse a Python file
|
|
5
|
+
def parse_python_file(file_path):
|
|
6
|
+
"""Parse a Python file with automatic encoding and BOM handling.
|
|
8
7
|
|
|
9
8
|
This function:
|
|
10
|
-
1. Reads the file with UTF-8
|
|
9
|
+
1. Reads the file with automatic encoding detection (UTF-8 → Latin-1)
|
|
11
10
|
2. Removes any BOM characters automatically
|
|
12
11
|
3. Parses the cleaned source code
|
|
13
12
|
|
|
@@ -20,12 +19,7 @@ def parse_python_file(file_path):
|
|
|
20
19
|
Raises:
|
|
21
20
|
SyntaxError: If source code has syntax errors
|
|
22
21
|
FileNotFoundError: If file doesn't exist
|
|
22
|
+
ValueError: If file encoding cannot be determined
|
|
23
23
|
"""
|
|
24
|
-
|
|
25
|
-
source = f.read()
|
|
26
|
-
|
|
27
|
-
# Remove BOM if present (handles files from Windows editors, etc.)
|
|
28
|
-
source = remove_bom(source)
|
|
29
|
-
|
|
30
|
-
return ast.parse(source)
|
|
24
|
+
return read_and_parse_python_file(file_path)
|
|
31
25
|
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
# Import Extractor Module
|
|
2
2
|
# analysis/import_extractor.py
|
|
3
3
|
|
|
4
|
-
import ast
|
|
5
|
-
from analysis.utils.bom_handler import
|
|
4
|
+
import ast
|
|
5
|
+
from analysis.utils.bom_handler import read_source_file, parse_source_to_ast
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def extract_imports(file_path):
|
|
9
|
-
"""Extract imports from a Python file
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# Remove BOM if present
|
|
14
|
-
source = remove_bom(source)
|
|
8
|
+
def extract_imports(file_path):
|
|
9
|
+
"""Extract imports from a Python file with automatic encoding and BOM handling."""
|
|
10
|
+
source = read_source_file(file_path)
|
|
11
|
+
tree = parse_source_to_ast(source, file_path=file_path)
|
|
15
12
|
|
|
16
|
-
tree = ast.parse(source)
|
|
17
13
|
imports = []
|
|
18
14
|
|
|
19
15
|
for node in ast.walk(tree):
|
|
@@ -5,10 +5,8 @@ from __future__ import annotations
|
|
|
5
5
|
|
|
6
6
|
from typing import Optional, Dict, Any
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import os
|
|
11
|
-
from analysis.utils.bom_handler import remove_bom
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
12
10
|
|
|
13
11
|
from analysis.indexing.symbol_index import SymbolIndex, SymbolInfo
|
|
14
12
|
from analysis.graph.callgraph_index import CallGraphIndex, CallSite
|
|
@@ -27,13 +25,10 @@ def collect_python_files(root_dir: str):
|
|
|
27
25
|
return py_files
|
|
28
26
|
|
|
29
27
|
|
|
30
|
-
def parse_ast(file_path: str)
|
|
31
|
-
"""Parse a Python file
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
# Remove BOM if present
|
|
35
|
-
source = remove_bom(source)
|
|
36
|
-
return ast.parse(source)
|
|
28
|
+
def parse_ast(file_path: str):
|
|
29
|
+
"""Parse a Python file with automatic encoding and BOM handling."""
|
|
30
|
+
from analysis.utils.bom_handler import read_and_parse_python_file
|
|
31
|
+
return read_and_parse_python_file(file_path)
|
|
37
32
|
|
|
38
33
|
|
|
39
34
|
def file_to_module(file_path: str, repo_root: str) -> str:
|
|
@@ -3,16 +3,14 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
from typing import Optional, Dict, Any, List
|
|
5
5
|
|
|
6
|
-
import os
|
|
7
|
-
import
|
|
8
|
-
import json
|
|
6
|
+
import os
|
|
7
|
+
import json
|
|
9
8
|
from analysis.indexing.symbol_index import SymbolIndex
|
|
10
9
|
from analysis.indexing.import_resolver import ImportResolver
|
|
11
10
|
from analysis.call_graph.cross_file_resolver import CrossFileResolver
|
|
12
11
|
from analysis.call_graph.call_extractor import extract_function_calls
|
|
13
12
|
from analysis.core.import_extractor import extract_imports
|
|
14
13
|
from analysis.graph.callgraph_index import build_caller_fqn
|
|
15
|
-
from analysis.utils.bom_handler import remove_bom
|
|
16
14
|
|
|
17
15
|
|
|
18
16
|
PROJECT_ROOT = os.path.dirname(os.path.dirname(__file__))
|
|
@@ -29,13 +27,10 @@ def collect_python_files(root_dir: str) -> List[str]:
|
|
|
29
27
|
return py_files
|
|
30
28
|
|
|
31
29
|
|
|
32
|
-
def parse_ast(file_path: str):
|
|
33
|
-
"""Parse a Python file, automatically handling UTF-8 BOM."""
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
# Remove BOM if present
|
|
37
|
-
source = remove_bom(source)
|
|
38
|
-
return ast.parse(source)
|
|
30
|
+
def parse_ast(file_path: str):
|
|
31
|
+
"""Parse a Python file, automatically handling encoding and UTF-8 BOM."""
|
|
32
|
+
from analysis.utils.bom_handler import read_and_parse_python_file
|
|
33
|
+
return read_and_parse_python_file(file_path)
|
|
39
34
|
|
|
40
35
|
|
|
41
36
|
def file_to_module(file_path: str, repo_root: str) -> str:
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""BOM (Byte Order Mark), encoding, and AST parsing utilities for CodeMap.
|
|
2
|
+
|
|
3
|
+
This module provides utilities to handle:
|
|
4
|
+
1. UTF-8 BOM (Byte Order Mark) characters added by certain editors
|
|
5
|
+
2. Non-UTF-8 encoded files (e.g., Latin-1, Windows-1252)
|
|
6
|
+
|
|
7
|
+
Issues handled:
|
|
8
|
+
- BOM (U+FEFF): invisible character causing "invalid non-printable character U+FEFF"
|
|
9
|
+
- Non-UTF-8: files with different encodings causing UnicodeDecodeError
|
|
10
|
+
|
|
11
|
+
Solution: Detect encoding with fallback chain, strip BOM, and parse quietly.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import ast
|
|
15
|
+
import warnings
|
|
16
|
+
from typing import Tuple
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def remove_bom(source: str) -> str:
|
|
20
|
+
"""Remove UTF-8 BOM (Byte Order Mark) from source code if present.
|
|
21
|
+
|
|
22
|
+
BOM is a special character (U+FEFF) that some editors (especially Notepad
|
|
23
|
+
on Windows) add to the start of files. Python's AST parser doesn't handle it.
|
|
24
|
+
|
|
25
|
+
This function silently removes it if present, or returns the source unchanged.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
source: Python source code as string
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Source code with BOM removed if present
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
>>> source_with_bom = '\\ufeffdef hello(): pass'
|
|
35
|
+
>>> clean_source = remove_bom(source_with_bom)
|
|
36
|
+
>>> print(clean_source)
|
|
37
|
+
def hello(): pass
|
|
38
|
+
"""
|
|
39
|
+
if source.startswith('\ufeff'):
|
|
40
|
+
return source[1:]
|
|
41
|
+
return source
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def detect_encoding(file_path: str) -> Tuple[str, bool]:
|
|
45
|
+
"""Detect file encoding by trying multiple decodings.
|
|
46
|
+
|
|
47
|
+
Tries encodings in this order:
|
|
48
|
+
1. UTF-8 (most common for Python files)
|
|
49
|
+
2. System default encoding
|
|
50
|
+
3. Latin-1 / ISO-8859-1 (accepts any byte sequence)
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
file_path: Path to file to detect encoding for
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Tuple of (encoding_name: str, is_fallback: bool)
|
|
57
|
+
is_fallback=True means file uses non-standard encoding
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
FileNotFoundError: If file doesn't exist
|
|
61
|
+
"""
|
|
62
|
+
import sys
|
|
63
|
+
|
|
64
|
+
encodings_to_try = [
|
|
65
|
+
('utf-8', False),
|
|
66
|
+
(sys.getdefaultencoding(), False),
|
|
67
|
+
('latin-1', True), # Latin-1 accepts any byte sequence
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
for encoding, is_fallback in encodings_to_try:
|
|
71
|
+
try:
|
|
72
|
+
with open(file_path, 'rb') as f:
|
|
73
|
+
f.read().decode(encoding)
|
|
74
|
+
return (encoding, is_fallback)
|
|
75
|
+
except (UnicodeDecodeError, LookupError):
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
# Should never reach here since Latin-1 accepts all bytes
|
|
79
|
+
return ('latin-1', True)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def read_source_file(file_path: str) -> str:
|
|
83
|
+
"""Read a Python file with automatic encoding detection and BOM removal.
|
|
84
|
+
|
|
85
|
+
Handles files with different encodings gracefully by trying multiple
|
|
86
|
+
decodings in order of likelihood, then falling back to Latin-1.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
file_path: Path to Python file to read
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Source code with BOM removed
|
|
93
|
+
|
|
94
|
+
Raises:
|
|
95
|
+
FileNotFoundError: If file doesn't exist
|
|
96
|
+
"""
|
|
97
|
+
encoding, _is_fallback = detect_encoding(file_path)
|
|
98
|
+
with open(file_path, 'r', encoding=encoding, errors='replace') as f:
|
|
99
|
+
source = f.read()
|
|
100
|
+
return remove_bom(source)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def parse_source_to_ast(source: str, file_path: str = "<unknown>") -> ast.AST:
|
|
104
|
+
"""Parse source code while suppressing noisy invalid-escape warnings.
|
|
105
|
+
|
|
106
|
+
Some user repositories contain regular string literals like ``"\\S"`` or
|
|
107
|
+
``"\\["``. Python can emit ``SyntaxWarning: invalid escape sequence`` while
|
|
108
|
+
parsing those files even though analysis can continue normally. For CodeMap,
|
|
109
|
+
these warnings are implementation noise, so we suppress them here.
|
|
110
|
+
"""
|
|
111
|
+
with warnings.catch_warnings():
|
|
112
|
+
warnings.filterwarnings("ignore", category=SyntaxWarning)
|
|
113
|
+
return ast.parse(source, filename=file_path)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def read_and_parse_python_file(file_path: str) -> ast.AST:
|
|
117
|
+
"""Read a Python file with encoding/BOM handling and return its AST."""
|
|
118
|
+
source = read_source_file(file_path)
|
|
119
|
+
return parse_source_to_ast(source, file_path=file_path)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Simple progress spinner for terminal output."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
from threading import Thread
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ProgressSpinner:
|
|
10
|
+
"""A simple terminal progress spinner with rotating animation.
|
|
11
|
+
|
|
12
|
+
Shows a rotating spinner animation while a long-running operation is in progress.
|
|
13
|
+
Useful for giving users feedback that the program is still working.
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
spinner = ProgressSpinner("Analyzing repository...")
|
|
17
|
+
spinner.start()
|
|
18
|
+
time.sleep(5) # Long operation
|
|
19
|
+
spinner.stop()
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
|
23
|
+
# Fallback for terminals that don't support Unicode
|
|
24
|
+
FRAMES_ASCII = ['|', '/', '-', '\\']
|
|
25
|
+
|
|
26
|
+
def __init__(self, message: str = "Processing..."):
|
|
27
|
+
"""Initialize the spinner.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
message: Text to display next to the spinner
|
|
31
|
+
"""
|
|
32
|
+
self.message = message
|
|
33
|
+
self.running = False
|
|
34
|
+
self.thread: Optional[Thread] = None
|
|
35
|
+
self.frame_index = 0
|
|
36
|
+
self._use_unicode = True
|
|
37
|
+
|
|
38
|
+
def _animate(self) -> None:
|
|
39
|
+
"""Animation loop for the spinner."""
|
|
40
|
+
frames = self.FRAMES if self._use_unicode else self.FRAMES_ASCII
|
|
41
|
+
|
|
42
|
+
while self.running:
|
|
43
|
+
frame = frames[self.frame_index % len(frames)]
|
|
44
|
+
# Use \r to return to start of line, overwrite previous output
|
|
45
|
+
sys.stderr.write(f"\r{frame} {self.message}")
|
|
46
|
+
sys.stderr.flush()
|
|
47
|
+
|
|
48
|
+
self.frame_index += 1
|
|
49
|
+
time.sleep(0.1) # Smooth animation at ~10 FPS
|
|
50
|
+
|
|
51
|
+
def start(self) -> None:
|
|
52
|
+
"""Start the spinner animation."""
|
|
53
|
+
if not self.running:
|
|
54
|
+
self.running = True
|
|
55
|
+
self.frame_index = 0
|
|
56
|
+
self.thread = Thread(target=self._animate, daemon=True)
|
|
57
|
+
self.thread.start()
|
|
58
|
+
|
|
59
|
+
def stop(self) -> None:
|
|
60
|
+
"""Stop the spinner and clear the line."""
|
|
61
|
+
if self.running:
|
|
62
|
+
self.running = False
|
|
63
|
+
if self.thread:
|
|
64
|
+
self.thread.join(timeout=1)
|
|
65
|
+
# Clear the spinner line
|
|
66
|
+
sys.stderr.write("\r" + " " * (len(self.message) + 4) + "\r")
|
|
67
|
+
sys.stderr.flush()
|
|
68
|
+
|
|
69
|
+
def update(self, message: str) -> None:
|
|
70
|
+
"""Update the message displayed with the spinner.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
message: New message text
|
|
74
|
+
"""
|
|
75
|
+
self.message = message
|
|
76
|
+
|
|
77
|
+
def __enter__(self):
|
|
78
|
+
"""Context manager entry."""
|
|
79
|
+
self.start()
|
|
80
|
+
return self
|
|
81
|
+
|
|
82
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
83
|
+
"""Context manager exit."""
|
|
84
|
+
self.stop()
|
|
85
|
+
return False
|
|
@@ -1112,6 +1112,7 @@ def api_analyze(args) -> int:
|
|
|
1112
1112
|
from analysis.architecture.dependency_cycles import compute_dependency_cycle_metrics
|
|
1113
1113
|
from analysis.architecture.risk_radar import compute_risk_radar
|
|
1114
1114
|
from analysis.utils.repo_fetcher import fetch_public_repo, fetch_public_repo_zip
|
|
1115
|
+
from analysis.utils.progress_spinner import ProgressSpinner
|
|
1115
1116
|
from analysis.utils.cache_manager import (
|
|
1116
1117
|
build_manifest,
|
|
1117
1118
|
collect_fingerprints,
|
|
@@ -1282,6 +1283,10 @@ def api_analyze(args) -> int:
|
|
|
1282
1283
|
r2 = {}
|
|
1283
1284
|
metrics = {}
|
|
1284
1285
|
|
|
1286
|
+
# Start progress spinner for large repositories
|
|
1287
|
+
spinner = ProgressSpinner("Analyzing repository...")
|
|
1288
|
+
spinner.start()
|
|
1289
|
+
|
|
1285
1290
|
try:
|
|
1286
1291
|
if rebuild_required:
|
|
1287
1292
|
r1 = run_phase4(
|
|
@@ -1399,9 +1404,11 @@ def api_analyze(args) -> int:
|
|
|
1399
1404
|
)
|
|
1400
1405
|
touch_last_accessed(repo_hash)
|
|
1401
1406
|
except Exception as e:
|
|
1407
|
+
spinner.stop()
|
|
1402
1408
|
print_json({"ok": False, "error": "ANALYZE_FAILED", "message": redact_secrets(str(e))})
|
|
1403
1409
|
return 1
|
|
1404
1410
|
|
|
1411
|
+
spinner.stop()
|
|
1405
1412
|
print_json({
|
|
1406
1413
|
"ok": True,
|
|
1407
1414
|
"source": source,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codemap-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Local Python code analysis tool - understand architecture, dependencies, and call graphs
|
|
5
5
|
Author-email: ADITYA <aditykushwaha69@gmail.com>
|
|
6
|
-
License: MIT
|
|
6
|
+
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/ADITYA-kus/codemap_ai
|
|
8
8
|
Project-URL: Repository, https://github.com/ADITYA-kus/codemap_ai.git
|
|
9
9
|
Project-URL: Issues, https://github.com/ADITYA-kus/codemap_ai/issues
|
|
@@ -11,7 +11,6 @@ Project-URL: Documentation, https://github.com/ADITYA-kus/codemap_ai#readme
|
|
|
11
11
|
Keywords: code-analysis,python,architecture,call-graph,cli,dashboard,local,privacy
|
|
12
12
|
Classifier: Development Status :: 4 - Beta
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
15
14
|
Classifier: Programming Language :: Python :: 3
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -159,22 +158,35 @@ codemap open --port 8000
|
|
|
159
158
|
|
|
160
159
|
### Cache Management
|
|
161
160
|
```bash
|
|
162
|
-
# List all analyzed repositories
|
|
161
|
+
# 📋 List all analyzed repositories and their cache info
|
|
163
162
|
codemap cache list
|
|
164
163
|
|
|
165
|
-
# Show cache
|
|
166
|
-
codemap cache info <
|
|
164
|
+
# 📊 Show detailed cache information for a specific repository
|
|
165
|
+
codemap cache info --path <repo_directory>
|
|
167
166
|
|
|
168
|
-
#
|
|
169
|
-
codemap cache
|
|
167
|
+
# ⏱️ Set cache retention policy (automatically clean old caches)
|
|
168
|
+
codemap cache retention --path <repo_directory> --days 30 --yes
|
|
170
169
|
|
|
171
|
-
#
|
|
172
|
-
codemap cache
|
|
170
|
+
# 🧹 Preview what would be cleaned (safe, no deletion)
|
|
171
|
+
codemap cache sweep --dry-run
|
|
173
172
|
|
|
174
|
-
#
|
|
175
|
-
codemap cache sweep
|
|
173
|
+
# 🧹 Actually clean up expired caches (requires --yes confirmation)
|
|
174
|
+
codemap cache sweep --yes
|
|
175
|
+
|
|
176
|
+
# 🗑️ Clear cache for a specific repository (preview first)
|
|
177
|
+
codemap cache clear --path <repo_directory> --dry-run
|
|
178
|
+
|
|
179
|
+
# 🗑️ Actually delete a repository's cache (requires --yes confirmation)
|
|
180
|
+
codemap cache clear --path <repo_directory> --yes
|
|
176
181
|
```
|
|
177
182
|
|
|
183
|
+
**Cache Management Tips:**
|
|
184
|
+
- ✅ Always use `--dry-run` first to preview changes
|
|
185
|
+
- ✅ Add `--yes` flag to skip confirmation (useful in scripts)
|
|
186
|
+
- ✅ Default retention is 14 days; adjust with `--days <number>`
|
|
187
|
+
- ✅ Cache is stored in: `~/.codemap_cache/` (varies by OS)
|
|
188
|
+
- ✅ Use `cache list` to see all cached repositories and their sizes
|
|
189
|
+
|
|
178
190
|
**Get GitHub Token (for private repos):**
|
|
179
191
|
1. Go to https://github.com/settings/tokens
|
|
180
192
|
2. Click "Generate new token" → "Generate new token (classic)"
|
|
@@ -45,6 +45,7 @@ analysis/utils/ast_helpers.py
|
|
|
45
45
|
analysis/utils/bom_handler.py
|
|
46
46
|
analysis/utils/cache_manager.py
|
|
47
47
|
analysis/utils/path_resolver.py
|
|
48
|
+
analysis/utils/progress_spinner.py
|
|
48
49
|
analysis/utils/repo_fetcher.py
|
|
49
50
|
codemap_python.egg-info/PKG-INFO
|
|
50
51
|
codemap_python.egg-info/SOURCES.txt
|
|
@@ -54,6 +55,7 @@ codemap_python.egg-info/requires.txt
|
|
|
54
55
|
codemap_python.egg-info/top_level.txt
|
|
55
56
|
tests/test_cache_cli_commands.py
|
|
56
57
|
tests/test_cache_retention.py
|
|
58
|
+
tests/test_cli_invalid_escape_warnings.py
|
|
57
59
|
tests/test_no_key_persistence.py
|
|
58
60
|
tests/test_registry_session_mode.py
|
|
59
61
|
tests/test_security_cli_integration.py
|
|
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "codemap-python"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.4"
|
|
8
8
|
description = "Local Python code analysis tool - understand architecture, dependencies, and call graphs"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
|
-
license =
|
|
11
|
+
license = "MIT"
|
|
12
12
|
authors = [
|
|
13
13
|
{name = "ADITYA", email = "aditykushwaha69@gmail.com"}
|
|
14
14
|
]
|
|
@@ -25,7 +25,6 @@ keywords = [
|
|
|
25
25
|
classifiers = [
|
|
26
26
|
"Development Status :: 4 - Beta",
|
|
27
27
|
"Intended Audience :: Developers",
|
|
28
|
-
"License :: OSI Approved :: MIT License",
|
|
29
28
|
"Programming Language :: Python :: 3",
|
|
30
29
|
"Programming Language :: Python :: 3.10",
|
|
31
30
|
"Programming Language :: Python :: 3.11",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestCliInvalidEscapeWarnings(unittest.TestCase):
|
|
12
|
+
def test_analyze_suppresses_invalid_escape_syntax_warnings(self):
|
|
13
|
+
repo_dir = os.path.join(PROJECT_ROOT, "tests", "_tmp_invalid_escape_repo")
|
|
14
|
+
shutil.rmtree(repo_dir, ignore_errors=True)
|
|
15
|
+
try:
|
|
16
|
+
os.makedirs(repo_dir, exist_ok=True)
|
|
17
|
+
with open(os.path.join(repo_dir, "warns.py"), "w", encoding="utf-8") as f:
|
|
18
|
+
f.write(
|
|
19
|
+
'PATTERNS = ["\\\\S", "\\\\[", "\\\\:", "\\\\d"]\n'
|
|
20
|
+
"def read_patterns():\n"
|
|
21
|
+
" return PATTERNS\n"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
cmd = [sys.executable, os.path.join(PROJECT_ROOT, "cli.py"), "analyze", "--path", repo_dir]
|
|
25
|
+
proc = subprocess.run(cmd, cwd=PROJECT_ROOT, capture_output=True, text=True, check=False)
|
|
26
|
+
|
|
27
|
+
self.assertEqual(proc.returncode, 0, msg=proc.stderr or proc.stdout)
|
|
28
|
+
self.assertNotIn("SyntaxWarning", proc.stderr)
|
|
29
|
+
self.assertNotIn("invalid escape sequence", proc.stderr)
|
|
30
|
+
finally:
|
|
31
|
+
shutil.rmtree(repo_dir, ignore_errors=True)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
if __name__ == "__main__":
|
|
35
|
+
unittest.main()
|
|
@@ -893,7 +893,11 @@ def _push_recent(items: List[str], value: str, limit: int = 20) -> List[str]:
|
|
|
893
893
|
|
|
894
894
|
@app.get("/", response_class=HTMLResponse)
|
|
895
895
|
def index(request: Request):
|
|
896
|
-
return templates.TemplateResponse(
|
|
896
|
+
return templates.TemplateResponse(
|
|
897
|
+
request=request,
|
|
898
|
+
name="index.html",
|
|
899
|
+
context={"request": request, "default_repo": DEFAULT_REPO}
|
|
900
|
+
)
|
|
897
901
|
|
|
898
902
|
|
|
899
903
|
@app.get("/api/workspace")
|
|
@@ -98,6 +98,15 @@
|
|
|
98
98
|
let graphDataCache = new Map();
|
|
99
99
|
let impactDataCache = new Map();
|
|
100
100
|
let architectureCache = null;
|
|
101
|
+
|
|
102
|
+
// Track which tabs have been loaded (for lazy loading optimization)
|
|
103
|
+
let tabsLoaded = {
|
|
104
|
+
details: true, // Details tab loads on symbol selection
|
|
105
|
+
graph: false, // Lazy load when clicked
|
|
106
|
+
impact: false, // Lazy load when clicked
|
|
107
|
+
architecture: false // Lazy load when clicked
|
|
108
|
+
};
|
|
109
|
+
|
|
101
110
|
let repoSummary = null;
|
|
102
111
|
let repoSummaryUpdatedAt = "";
|
|
103
112
|
let repoSummaryStatus = "idle"; // idle|loading|ready|missing|error
|
|
@@ -523,26 +532,61 @@
|
|
|
523
532
|
const isImpact = activeTab === "impact";
|
|
524
533
|
const isGraph = activeTab === "graph";
|
|
525
534
|
const isArchitecture = activeTab === "architecture";
|
|
535
|
+
|
|
536
|
+
// Update tab button states
|
|
526
537
|
if (tabDetailsEl) tabDetailsEl.classList.toggle("active", activeTab === "details");
|
|
527
538
|
if (tabImpactEl) tabImpactEl.classList.toggle("active", isImpact);
|
|
528
539
|
if (tabGraphEl) tabGraphEl.classList.toggle("active", isGraph);
|
|
529
540
|
if (tabArchitectureEl) tabArchitectureEl.classList.toggle("active", isArchitecture);
|
|
541
|
+
|
|
542
|
+
// Show/hide tab content
|
|
530
543
|
if (symbolViewEl) symbolViewEl.classList.toggle("hidden", activeTab !== "details");
|
|
531
544
|
if (impactViewEl) impactViewEl.classList.toggle("hidden", !isImpact);
|
|
532
545
|
if (graphViewEl) graphViewEl.classList.toggle("hidden", !isGraph);
|
|
533
546
|
if (architectureViewEl) architectureViewEl.classList.toggle("hidden", !isArchitecture);
|
|
534
547
|
if (graphControlsEl) graphControlsEl.classList.toggle("hidden", !isGraph);
|
|
535
548
|
if (impactControlsEl) impactControlsEl.classList.toggle("hidden", !isImpact);
|
|
536
|
-
|
|
549
|
+
|
|
550
|
+
// Lazy loading: Only load data if not already loaded
|
|
551
|
+
// This prevents repeated API calls and speed up tab switching
|
|
552
|
+
if (isGraph && !tabsLoaded.graph) {
|
|
553
|
+
showLoadingIndicator(graphViewEl, "Loading call graph...");
|
|
537
554
|
loadGraph();
|
|
538
|
-
|
|
539
|
-
if (
|
|
555
|
+
tabsLoaded.graph = true;
|
|
556
|
+
} else if (isGraph && tabsLoaded.graph) {
|
|
557
|
+
// Already loaded, just make sure content is visible
|
|
558
|
+
if (graphViewEl) graphViewEl.classList.remove("hidden");
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (isImpact && !tabsLoaded.impact) {
|
|
562
|
+
showLoadingIndicator(impactViewEl, "Loading impact analysis...");
|
|
540
563
|
loadImpact();
|
|
564
|
+
tabsLoaded.impact = true;
|
|
565
|
+
} else if (isImpact && tabsLoaded.impact) {
|
|
566
|
+
if (impactViewEl) impactViewEl.classList.remove("hidden");
|
|
541
567
|
}
|
|
542
|
-
|
|
568
|
+
|
|
569
|
+
if (isArchitecture && !tabsLoaded.architecture) {
|
|
570
|
+
showLoadingIndicator(architectureViewEl, "Loading architecture insights...");
|
|
543
571
|
loadArchitecture();
|
|
572
|
+
tabsLoaded.architecture = true;
|
|
573
|
+
} else if (isArchitecture && tabsLoaded.architecture) {
|
|
574
|
+
if (architectureViewEl) architectureViewEl.classList.remove("hidden");
|
|
544
575
|
}
|
|
545
576
|
}
|
|
577
|
+
|
|
578
|
+
// Show loading indicator in a view element
|
|
579
|
+
function showLoadingIndicator(el, message) {
|
|
580
|
+
if (!el) return;
|
|
581
|
+
el.classList.remove("muted", "hidden");
|
|
582
|
+
el.innerHTML = `<div class="card" style="padding: 20px; text-align: center;">
|
|
583
|
+
<div style="display: inline-block; width: 24px; height: 24px; border: 3px solid #ccc; border-top-color: var(--accent); border-radius: 50%; animation: spin 0.8s linear infinite; margin-right: 10px;"></div>
|
|
584
|
+
<span>${esc(message)}</span>
|
|
585
|
+
</div>
|
|
586
|
+
<style>
|
|
587
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
588
|
+
</style>`;
|
|
589
|
+
}
|
|
546
590
|
|
|
547
591
|
function shortLabel(fqn) {
|
|
548
592
|
const parts = String(fqn || "").split(".");
|
|
@@ -2585,6 +2629,12 @@
|
|
|
2585
2629
|
highlightActiveSymbol();
|
|
2586
2630
|
graphDataCache.clear();
|
|
2587
2631
|
impactDataCache.clear();
|
|
2632
|
+
|
|
2633
|
+
// Allow graph and impact tabs to be reloaded for the new symbol
|
|
2634
|
+
// (Keep details: true since we're loading it now)
|
|
2635
|
+
tabsLoaded.graph = false;
|
|
2636
|
+
tabsLoaded.impact = false;
|
|
2637
|
+
|
|
2588
2638
|
showSymbolLoading();
|
|
2589
2639
|
try {
|
|
2590
2640
|
const symbolPromise = (async () => {
|
|
@@ -2716,6 +2766,15 @@
|
|
|
2716
2766
|
riskRadarUpdatedAt = "";
|
|
2717
2767
|
riskRadarStatus = "idle";
|
|
2718
2768
|
riskRadarError = "";
|
|
2769
|
+
|
|
2770
|
+
// Reset tab loading state for new repo (enables lazy loading again)
|
|
2771
|
+
tabsLoaded = {
|
|
2772
|
+
details: true,
|
|
2773
|
+
graph: false,
|
|
2774
|
+
impact: false,
|
|
2775
|
+
architecture: false
|
|
2776
|
+
};
|
|
2777
|
+
|
|
2719
2778
|
await loadRepoRegistry();
|
|
2720
2779
|
const okMeta = await loadMeta();
|
|
2721
2780
|
await loadDataPrivacy();
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
"""BOM (Byte Order Mark) handling utilities for CodeMap.
|
|
2
|
-
|
|
3
|
-
This module provides utilities to handle UTF-8 BOM characters that are
|
|
4
|
-
sometimes added to Python files by certain editors (especially on Windows).
|
|
5
|
-
|
|
6
|
-
BOM (U+FEFF) is an invisible character that Python's AST parser cannot handle,
|
|
7
|
-
causing: "invalid non-printable character U+FEFF"
|
|
8
|
-
|
|
9
|
-
Solution: Strip BOM before parsing Python files.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def remove_bom(source: str) -> str:
|
|
14
|
-
"""Remove UTF-8 BOM (Byte Order Mark) from source code if present.
|
|
15
|
-
|
|
16
|
-
BOM is a special character (U+FEFF) that some editors (especially Notepad
|
|
17
|
-
on Windows) add to the start of files. Python's AST parser doesn't handle it.
|
|
18
|
-
|
|
19
|
-
This function silently removes it if present, or returns the source unchanged.
|
|
20
|
-
|
|
21
|
-
Args:
|
|
22
|
-
source: Python source code as string
|
|
23
|
-
|
|
24
|
-
Returns:
|
|
25
|
-
Source code with BOM removed if present
|
|
26
|
-
|
|
27
|
-
Example:
|
|
28
|
-
>>> source_with_bom = '\\ufeffdef hello(): pass'
|
|
29
|
-
>>> clean_source = remove_bom(source_with_bom)
|
|
30
|
-
>>> print(clean_source)
|
|
31
|
-
def hello(): pass
|
|
32
|
-
"""
|
|
33
|
-
if source.startswith('\ufeff'):
|
|
34
|
-
return source[1:]
|
|
35
|
-
return source
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def read_source_file(file_path: str) -> str:
|
|
39
|
-
"""Read a Python file and remove BOM if present.
|
|
40
|
-
|
|
41
|
-
This is a convenience function that combines file reading with BOM removal.
|
|
42
|
-
|
|
43
|
-
Args:
|
|
44
|
-
file_path: Path to Python file to read
|
|
45
|
-
|
|
46
|
-
Returns:
|
|
47
|
-
Source code with BOM removed
|
|
48
|
-
|
|
49
|
-
Raises:
|
|
50
|
-
FileNotFoundError: If file doesn't exist
|
|
51
|
-
UnicodeDecodeError: If file encoding is not UTF-8
|
|
52
|
-
"""
|
|
53
|
-
with open(file_path, "r", encoding="utf-8") as f:
|
|
54
|
-
source = f.read()
|
|
55
|
-
return remove_bom(source)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|