error-translator-cli-v2 2.0.0__tar.gz → 3.0.2__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.
- {error_translator_cli_v2-2.0.0 → error_translator_cli_v2-3.0.2}/PKG-INFO +2 -23
- {error_translator_cli_v2-2.0.0 → error_translator_cli_v2-3.0.2}/pyproject.toml +6 -5
- error_translator_cli_v2-3.0.2/setup.py +16 -0
- error_translator_cli_v2-3.0.2/src/error_translator/__init__.py +7 -0
- error_translator_cli_v2-3.0.2/src/error_translator/api/server.py +94 -0
- error_translator_cli_v2-3.0.2/src/error_translator/ast/ast_engine.py +132 -0
- error_translator_cli_v2-3.0.2/src/error_translator/ast/ast_handlers.py +63 -0
- error_translator_cli_v2-3.0.2/src/error_translator/auto.py +37 -0
- error_translator_cli_v2-3.0.2/src/error_translator/banner.py +53 -0
- error_translator_cli_v2-3.0.2/src/error_translator/cli.py +75 -0
- error_translator_cli_v2-3.0.2/src/error_translator/core.py +109 -0
- error_translator_cli_v2-3.0.2/src/error_translator/ext/fast_matcher.c +95 -0
- error_translator_cli_v2-3.0.2/src/error_translator/parser.py +43 -0
- error_translator_cli_v2-3.0.2/src/error_translator/rules.py +40 -0
- error_translator_cli_v2-3.0.2/src/error_translator/runner.py +55 -0
- error_translator_cli_v2-3.0.2/src/error_translator/ui.py +161 -0
- {error_translator_cli_v2-2.0.0 → error_translator_cli_v2-3.0.2}/src/error_translator_cli_v2.egg-info/PKG-INFO +2 -23
- {error_translator_cli_v2-2.0.0 → error_translator_cli_v2-3.0.2}/src/error_translator_cli_v2.egg-info/SOURCES.txt +11 -0
- error_translator_cli_v2-3.0.2/tests/test_ast.py +41 -0
- {error_translator_cli_v2-2.0.0 → error_translator_cli_v2-3.0.2}/tests/test_core.py +17 -0
- error_translator_cli_v2-2.0.0/src/error_translator/__init__.py +0 -0
- error_translator_cli_v2-2.0.0/src/error_translator/auto.py +0 -22
- error_translator_cli_v2-2.0.0/src/error_translator/cli.py +0 -265
- error_translator_cli_v2-2.0.0/src/error_translator/core.py +0 -113
- {error_translator_cli_v2-2.0.0 → error_translator_cli_v2-3.0.2}/LICENSE +0 -0
- {error_translator_cli_v2-2.0.0 → error_translator_cli_v2-3.0.2}/README.md +0 -0
- {error_translator_cli_v2-2.0.0 → error_translator_cli_v2-3.0.2}/setup.cfg +0 -0
- {error_translator_cli_v2-2.0.0 → error_translator_cli_v2-3.0.2}/src/error_translator/rules.json +0 -0
- {error_translator_cli_v2-2.0.0 → error_translator_cli_v2-3.0.2}/src/error_translator_cli_v2.egg-info/dependency_links.txt +0 -0
- {error_translator_cli_v2-2.0.0 → error_translator_cli_v2-3.0.2}/src/error_translator_cli_v2.egg-info/entry_points.txt +0 -0
- {error_translator_cli_v2-2.0.0 → error_translator_cli_v2-3.0.2}/src/error_translator_cli_v2.egg-info/requires.txt +0 -0
- {error_translator_cli_v2-2.0.0 → error_translator_cli_v2-3.0.2}/src/error_translator_cli_v2.egg-info/top_level.txt +0 -0
|
@@ -1,30 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: error-translator-cli-v2
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.2
|
|
4
4
|
Summary: Offline Python traceback translator and error explainer CLI that converts exceptions into clear explanations and actionable fixes.
|
|
5
5
|
Author-email: Gourabananda Datta <gourabanandadatta@zohomail.in>
|
|
6
|
-
License: MIT
|
|
7
|
-
|
|
8
|
-
Copyright (c) 2026 Gourabananda Datta
|
|
9
|
-
|
|
10
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
-
in the Software without restriction, including without limitation the rights
|
|
13
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
-
furnished to do so, subject to the following conditions:
|
|
16
|
-
|
|
17
|
-
The above copyright notice and this permission notice shall be included in all
|
|
18
|
-
copies or substantial portions of the Software.
|
|
19
|
-
|
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
-
SOFTWARE.
|
|
27
|
-
|
|
6
|
+
License: MIT
|
|
28
7
|
Project-URL: Homepage, https://github.com/gourabanandad/error-translator-cli-v2
|
|
29
8
|
Project-URL: Source, https://github.com/gourabanandad/error-translator-cli-v2
|
|
30
9
|
Project-URL: Documentation, https://gourabanandad.github.io/error-translator-cli-v2/
|
|
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "error-translator-cli-v2"
|
|
7
|
-
version = "
|
|
7
|
+
version = "3.0.2"
|
|
8
8
|
description = "Offline Python traceback translator and error explainer CLI that converts exceptions into clear explanations and actionable fixes."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
11
|
-
license = {
|
|
11
|
+
license = { text = "MIT" }
|
|
12
12
|
authors = [
|
|
13
13
|
{ name = "Gourabananda Datta", email = "gourabanandadatta@zohomail.in" }
|
|
14
14
|
]
|
|
@@ -62,12 +62,13 @@ server = ["fastapi>=0.110", "pydantic>=2.6", "uvicorn>=0.29"]
|
|
|
62
62
|
docs = ["mkdocs>=1.6", "mkdocs-material>=9.7", "pymdown-extensions>=10.0"]
|
|
63
63
|
dev = ["pytest>=8.0", "build>=1.2", "twine>=5.0"]
|
|
64
64
|
|
|
65
|
-
[tool.setuptools]
|
|
66
|
-
|
|
67
|
-
packages = ["error_translator"]
|
|
65
|
+
[tool.setuptools.packages.find]
|
|
66
|
+
where = ["src"]
|
|
68
67
|
|
|
69
68
|
[tool.pytest.ini_options]
|
|
69
|
+
testpaths = ["tests"]
|
|
70
70
|
pythonpath = ["src"]
|
|
71
|
+
addopts = "--cov=error_translator --cov-report=term-missing"
|
|
71
72
|
|
|
72
73
|
[tool.setuptools.package-data]
|
|
73
74
|
error_translator = ["rules.json", "static/*.html", "static/*.css"]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from setuptools import setup, Extension
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
# Windows (MSVC) uses /O2, Linux/Mac (GCC/Clang) uses -O3
|
|
5
|
+
compile_args = ['/O2'] if sys.platform == 'win32' else ['-O3']
|
|
6
|
+
|
|
7
|
+
fast_matcher_module = Extension(
|
|
8
|
+
'error_translator.fast_matcher',
|
|
9
|
+
sources=['src/error_translator/ext/fast_matcher.c'],
|
|
10
|
+
extra_compile_args=compile_args
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
setup(
|
|
15
|
+
ext_modules=[fast_matcher_module]
|
|
16
|
+
)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI Server for Error Translation.
|
|
3
|
+
|
|
4
|
+
This module exposes the error translation functionality as a RESTful web API.
|
|
5
|
+
It includes endpoints for single and batch translations, and serves a static
|
|
6
|
+
web interface. Useful for integrating the translator into web apps or distributed systems.
|
|
7
|
+
"""
|
|
8
|
+
from src.error_translator.core import translate_error
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
from fastapi import FastAPI
|
|
11
|
+
from fastapi.staticfiles import StaticFiles
|
|
12
|
+
from fastapi.responses import FileResponse
|
|
13
|
+
import os
|
|
14
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
15
|
+
import asyncio
|
|
16
|
+
from typing import List
|
|
17
|
+
|
|
18
|
+
# Try to determine the package version to expose in the API metadata.
|
|
19
|
+
try:
|
|
20
|
+
VERSION = version("error-translator-cli-v2")
|
|
21
|
+
except PackageNotFoundError:
|
|
22
|
+
VERSION = "unknown (not installed via pip)"
|
|
23
|
+
|
|
24
|
+
# Initialize the FastAPI application with basic metadata
|
|
25
|
+
app = FastAPI(
|
|
26
|
+
title="Error translator API",
|
|
27
|
+
description="An API that translates Python errors into human-readable English.",
|
|
28
|
+
version=VERSION
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# --- Pydantic Models for Request Validation ---
|
|
32
|
+
|
|
33
|
+
class ErrorRequest(BaseModel):
|
|
34
|
+
"""Schema for a single error translation request."""
|
|
35
|
+
traceback_setting: str
|
|
36
|
+
|
|
37
|
+
class BatchErrorRequest(BaseModel):
|
|
38
|
+
"""Schema for translating multiple errors in a single request."""
|
|
39
|
+
tracebacks: List[str]
|
|
40
|
+
|
|
41
|
+
# --- API Endpoints ---
|
|
42
|
+
|
|
43
|
+
@app.post("/translate")
|
|
44
|
+
def translation_endpoint(request: ErrorRequest):
|
|
45
|
+
"""
|
|
46
|
+
Translates a single Python traceback.
|
|
47
|
+
Accepts a JSON payload with a `traceback_setting` field and returns the translation.
|
|
48
|
+
"""
|
|
49
|
+
translation_result = translate_error(request.traceback_setting)
|
|
50
|
+
return translation_result
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@app.post("/translate/batch")
|
|
54
|
+
async def batch_translation_endoint(request: BatchErrorRequest):
|
|
55
|
+
"""
|
|
56
|
+
Translates an array of tracebacks concurrently using asyncio.
|
|
57
|
+
Ideal for processing bulk logs from message queues or distributed systems.
|
|
58
|
+
"""
|
|
59
|
+
# Create an async task for each traceback to run them in parallel
|
|
60
|
+
tasks = [
|
|
61
|
+
asyncio.to_thread(translate_error, tb) for tb in request.tracebacks
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
# Gather results from all tasks
|
|
65
|
+
results = await asyncio.gather(*tasks)
|
|
66
|
+
return {"translate_errors": results}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@app.get("/")
|
|
70
|
+
def read_root():
|
|
71
|
+
"""
|
|
72
|
+
Serves the web UI index.html file at the root URL.
|
|
73
|
+
Provides a simple browser-based interface for the translator.
|
|
74
|
+
"""
|
|
75
|
+
index_path = os.path.join(os.path.dirname(__file__), "static", "index.html")
|
|
76
|
+
if os.path.exists(index_path):
|
|
77
|
+
return FileResponse(index_path)
|
|
78
|
+
return {
|
|
79
|
+
"message": "Error translation API is running. Web UI not found. Please ensure static/index.html exists."
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@app.get("/health")
|
|
84
|
+
def health_check():
|
|
85
|
+
"""
|
|
86
|
+
Health check endpoint useful for monitoring systems (e.g., Kubernetes, Docker).
|
|
87
|
+
"""
|
|
88
|
+
return {"status": "ok"}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# Mount static files directory (CSS, JS, images, etc.) so they can be loaded by the web UI
|
|
92
|
+
static_path = os.path.join(os.path.dirname(__file__), "static")
|
|
93
|
+
if os.path.exists(static_path):
|
|
94
|
+
app.mount("/static", StaticFiles(directory=static_path), name="static")
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import difflib
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
class ScopedSymbolCollector(ast.NodeVisitor):
|
|
6
|
+
"""
|
|
7
|
+
Walks the Abstract Syntax Tree (AST) of the target Python file.
|
|
8
|
+
It collects variables, functions, classes, and attributes defined in the code.
|
|
9
|
+
|
|
10
|
+
Crucially, it respects lexical scope by using the crash line number.
|
|
11
|
+
It will only descend into function or class bodies if the crash actually
|
|
12
|
+
happened inside them, preventing it from suggesting variables from unrelated scopes.
|
|
13
|
+
"""
|
|
14
|
+
def __init__(self, target_line: int):
|
|
15
|
+
self.target_line = target_line
|
|
16
|
+
self.names = set() # Variables (e.g., local/global variable assignments)
|
|
17
|
+
self.attributes = set() # Object attributes/methods (e.g., obj.method_name)
|
|
18
|
+
self.classes = set() # Class names defined in the scope
|
|
19
|
+
self.functions = set() # Function names defined in the scope
|
|
20
|
+
self.imports = set() # Imported modules or aliases
|
|
21
|
+
|
|
22
|
+
def visit_Name(self, node):
|
|
23
|
+
"""Collects variable names when they are assigned (Store context)."""
|
|
24
|
+
if isinstance(node.ctx, ast.Store):
|
|
25
|
+
self.names.add(node.id)
|
|
26
|
+
self.generic_visit(node)
|
|
27
|
+
|
|
28
|
+
def visit_FunctionDef(self, node):
|
|
29
|
+
"""
|
|
30
|
+
Collects the function name into the current scope.
|
|
31
|
+
Only visits the function's internal body if the error line is within it.
|
|
32
|
+
"""
|
|
33
|
+
# The function's name is always added to the enclosing scope
|
|
34
|
+
self.names.add(node.name)
|
|
35
|
+
self.functions.add(node.name)
|
|
36
|
+
|
|
37
|
+
# SCOPING LOGIC: Only visit the body if the crash happened INSIDE this function
|
|
38
|
+
if hasattr(node, 'lineno') and hasattr(node, 'end_lineno'):
|
|
39
|
+
if node.lineno <= self.target_line <= node.end_lineno:
|
|
40
|
+
self.generic_visit(node)
|
|
41
|
+
else:
|
|
42
|
+
self.generic_visit(node)
|
|
43
|
+
|
|
44
|
+
def visit_ClassDef(self, node):
|
|
45
|
+
"""
|
|
46
|
+
Collects the class name into the current scope.
|
|
47
|
+
Only visits the class's internal body if the error line is within it.
|
|
48
|
+
"""
|
|
49
|
+
# The class name is always added to the enclosing scope
|
|
50
|
+
self.names.add(node.name)
|
|
51
|
+
self.classes.add(node.name)
|
|
52
|
+
|
|
53
|
+
# SCOPING LOGIC: Only visit the body if the crash happened INSIDE this class
|
|
54
|
+
if hasattr(node, 'lineno') and hasattr(node, 'end_lineno'):
|
|
55
|
+
if node.lineno <= self.target_line <= node.end_lineno:
|
|
56
|
+
self.generic_visit(node)
|
|
57
|
+
else:
|
|
58
|
+
self.generic_visit(node)
|
|
59
|
+
|
|
60
|
+
def visit_Attribute(self, node):
|
|
61
|
+
"""Collects attribute accesses (e.g., node.attr)."""
|
|
62
|
+
self.attributes.add(node.attr)
|
|
63
|
+
self.generic_visit(node)
|
|
64
|
+
|
|
65
|
+
def visit_Import(self, node):
|
|
66
|
+
"""Collects basic import names or aliases."""
|
|
67
|
+
for alias in node.names:
|
|
68
|
+
name = alias.asname if alias.asname else alias.name
|
|
69
|
+
self.imports.add(name)
|
|
70
|
+
self.names.add(name)
|
|
71
|
+
self.generic_visit(node)
|
|
72
|
+
|
|
73
|
+
def visit_ImportFrom(self, node):
|
|
74
|
+
"""Collects specific names imported from a module."""
|
|
75
|
+
for alias in node.names:
|
|
76
|
+
name = alias.asname if alias.asname else alias.name
|
|
77
|
+
self.imports.add(name)
|
|
78
|
+
self.names.add(name)
|
|
79
|
+
self.generic_visit(node)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def get_ast_suggestions(filepath: str, line_number: str, target_word: str, error_type: str) -> str:
|
|
83
|
+
"""
|
|
84
|
+
Parses a Python file into an AST, extracts available symbols for the crashing scope,
|
|
85
|
+
and uses string matching (difflib) to find the closest suggestion to a misspelled word.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
filepath: Absolute path to the Python file that crashed.
|
|
89
|
+
line_number: The line number where the error was thrown.
|
|
90
|
+
target_word: The misspelled variable, attribute, or import.
|
|
91
|
+
error_type: The type of error (NameError, AttributeError, etc.) to determine the pool of words.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
str: The suggested correct spelling, or None if no close match is found.
|
|
95
|
+
"""
|
|
96
|
+
if not os.path.exists(filepath) or filepath == "Unknown File":
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
target_line = int(line_number) if line_number.isdigit() else 0
|
|
101
|
+
|
|
102
|
+
# Read the crashing source file
|
|
103
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
104
|
+
source_code = f.read()
|
|
105
|
+
|
|
106
|
+
# Parse it into an Abstract Syntax Tree
|
|
107
|
+
tree = ast.parse(source_code)
|
|
108
|
+
|
|
109
|
+
# Walk the tree and collect symbols visible at the target line
|
|
110
|
+
collector = ScopedSymbolCollector(target_line)
|
|
111
|
+
collector.visit(tree)
|
|
112
|
+
|
|
113
|
+
# Determine the search pool based on the type of error
|
|
114
|
+
pool = set()
|
|
115
|
+
if error_type == "NameError":
|
|
116
|
+
pool = collector.names | collector.functions | collector.classes
|
|
117
|
+
elif error_type == "AttributeError":
|
|
118
|
+
pool = collector.attributes | collector.functions
|
|
119
|
+
elif error_type in ("ImportError", "ModuleNotFoundError"):
|
|
120
|
+
pool = collector.classes | collector.functions | collector.names | collector.imports
|
|
121
|
+
|
|
122
|
+
# Use difflib to find the most similar symbol in the pool
|
|
123
|
+
# Cutoff=0.6 means the suggestion must be at least 60% similar to the target word
|
|
124
|
+
matches = difflib.get_close_matches(target_word, pool, n=1, cutoff=0.6)
|
|
125
|
+
|
|
126
|
+
if matches:
|
|
127
|
+
return matches[0]
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
except Exception:
|
|
131
|
+
# If parsing or processing fails (e.g., SyntaxError in the file), fail gracefully
|
|
132
|
+
return None
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AST Handlers for error translation.
|
|
3
|
+
|
|
4
|
+
This module contains strategy functions (handlers) that provide deeper insights into
|
|
5
|
+
specific error types (like NameError, AttributeError, ImportError). It uses Abstract
|
|
6
|
+
Syntax Tree (AST) analysis to look at the exact code context and suggest valid
|
|
7
|
+
alternative variable names, attributes, or imports based on what is actually available
|
|
8
|
+
in the scope.
|
|
9
|
+
"""
|
|
10
|
+
from .ast_engine import get_ast_suggestions
|
|
11
|
+
|
|
12
|
+
# --- 1. THE INDIVIDUAL STRATEGIES (PLUGINS) ---
|
|
13
|
+
# Each handler is responsible for analyzing the AST for a specific type of error
|
|
14
|
+
# and returning actionable insights to the user.
|
|
15
|
+
|
|
16
|
+
def handle_name_error(file_path: str, line_number: str, extracted_values: list) -> str:
|
|
17
|
+
"""
|
|
18
|
+
Handles NameError: name 'X' is not defined.
|
|
19
|
+
Uses AST analysis to look for typos in variable names within the scope.
|
|
20
|
+
"""
|
|
21
|
+
bad_name = extracted_values[0] if extracted_values else ""
|
|
22
|
+
|
|
23
|
+
suggestion = get_ast_suggestions(file_path, line_number, bad_name, "NameError")
|
|
24
|
+
|
|
25
|
+
if suggestion:
|
|
26
|
+
return f"Did you mean '{suggestion}'? There appears to be a typo."
|
|
27
|
+
return f"The variable '{bad_name}' was not found in the file's scope. Ensure it is defined before this line."
|
|
28
|
+
|
|
29
|
+
def handle_attribute_error(file_path: str, line_number: str, extracted_values: list) -> str:
|
|
30
|
+
"""
|
|
31
|
+
Handles AttributeError: 'X' object has no attribute 'Y'.
|
|
32
|
+
Uses AST analysis to suggest similarly-named attributes or methods on the object.
|
|
33
|
+
"""
|
|
34
|
+
# Assuming the last extracted value is the missing attribute (e.g., 'apend')
|
|
35
|
+
bad_attr = extracted_values[-1] if extracted_values else ""
|
|
36
|
+
suggestion = get_ast_suggestions(file_path, line_number, bad_attr, "AttributeError")
|
|
37
|
+
|
|
38
|
+
if suggestion:
|
|
39
|
+
return f"Did you mean the attribute or method '{suggestion}'?"
|
|
40
|
+
return f"The attribute or method '{bad_attr}' was not found in the file's scope. Ensure it is defined before this line."
|
|
41
|
+
|
|
42
|
+
def handle_import_error(file_path: str, line_number: str, extracted_values: list) -> str:
|
|
43
|
+
"""
|
|
44
|
+
Handles ImportError / ModuleNotFoundError.
|
|
45
|
+
Uses AST analysis to verify if the class/function being imported exists or suggests fixes.
|
|
46
|
+
"""
|
|
47
|
+
bad_name = extracted_values[0] if extracted_values else ""
|
|
48
|
+
|
|
49
|
+
suggestion = get_ast_suggestions(file_path, line_number, bad_name, "ImportError")
|
|
50
|
+
if suggestion:
|
|
51
|
+
return f"Did you mean to import '{suggestion}' instead?"
|
|
52
|
+
return f"Verify that the class/function exists in the target module and is spelled correctly."
|
|
53
|
+
|
|
54
|
+
# --- 2. THE REGISTRY (THE MAGIC ROUTER) ---
|
|
55
|
+
# We map the string name of the error to the function that handles it.
|
|
56
|
+
# The core engine uses this registry to dynamically dispatch AST insights based on the error type.
|
|
57
|
+
AST_REGISTRY = {
|
|
58
|
+
"NameError": handle_name_error,
|
|
59
|
+
"AttributeError": handle_attribute_error,
|
|
60
|
+
"ImportError": handle_import_error,
|
|
61
|
+
# Additional error handlers can be registered here as new strategies are developed.
|
|
62
|
+
# "SyntaxError": handle_syntax_error,
|
|
63
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Auto-translator hook module.
|
|
3
|
+
|
|
4
|
+
When imported, this module automatically overrides the default Python exception handler
|
|
5
|
+
(sys.excepthook). Instead of a standard traceback, it intercepts any unhandled exception,
|
|
6
|
+
translates it, and prints a user-friendly explanation using the CLI output formatting.
|
|
7
|
+
"""
|
|
8
|
+
import sys
|
|
9
|
+
import traceback
|
|
10
|
+
from .core import translate_error
|
|
11
|
+
from .ui import print_result
|
|
12
|
+
|
|
13
|
+
def magic_hook(exc_type, exc_value, tb):
|
|
14
|
+
"""
|
|
15
|
+
Custom exception hook that intercepts unhandled Python exceptions before
|
|
16
|
+
they are printed to the terminal. It formats the standard traceback,
|
|
17
|
+
translates the error into human-readable advice, and prints it using
|
|
18
|
+
the rich UI components.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
exc_type: The type of the exception (e.g., ValueError, NameError).
|
|
22
|
+
exc_value: The exception instance itself.
|
|
23
|
+
tb: The traceback object containing the call stack.
|
|
24
|
+
"""
|
|
25
|
+
# 1. Convert the raw crash data into a standard traceback string
|
|
26
|
+
tb_lines = traceback.format_exception(exc_type, exc_value, tb)
|
|
27
|
+
tb_string = "".join(tb_lines)
|
|
28
|
+
|
|
29
|
+
# 2. Pass the standard traceback through our translation engine
|
|
30
|
+
result = translate_error(tb_string)
|
|
31
|
+
|
|
32
|
+
# 3. Print our beautiful colorized output instead of the default Python crash
|
|
33
|
+
print_result(result)
|
|
34
|
+
|
|
35
|
+
# Replace Python's default sys.excepthook with our custom translation hook.
|
|
36
|
+
# Any unhandled exception will now automatically be intercepted and translated.
|
|
37
|
+
sys.excepthook = magic_hook
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Installation banner module for Error Translator CLI.
|
|
3
|
+
This module prints a stylish banner to the terminal during installation.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
def print_install_banner():
|
|
7
|
+
"""Prints a stylish banner using ANSI escape codes for colors."""
|
|
8
|
+
RED = '\033[91m'
|
|
9
|
+
BLUE = '\033[94m'
|
|
10
|
+
CYAN = '\033[96m'
|
|
11
|
+
MAGENTA = '\033[95m'
|
|
12
|
+
YELLOW = '\033[93m'
|
|
13
|
+
WHITE = '\033[97m'
|
|
14
|
+
RESET = '\033[0m'
|
|
15
|
+
BOLD = '\033[1m'
|
|
16
|
+
|
|
17
|
+
error_art = r"""
|
|
18
|
+
███████╗██████╗ ██████╗ ██████╗ ██████╗
|
|
19
|
+
██╔════╝██╔══██╗██╔══██╗██╔═══██╗██╔══██╗
|
|
20
|
+
█████╗ ██████╔╝██████╔╝██║ ██║██████╔╝
|
|
21
|
+
██╔══╝ ██╔══██╗██╔══██╗██║ ██║██╔══██╗
|
|
22
|
+
███████╗██║ ██║██║ ██║╚██████╔╝██║ ██║
|
|
23
|
+
╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
translator_art = r"""
|
|
27
|
+
████████╗██████╗ █████╗ ███╗ ██╗███████╗██╗ █████╗ ████████╗ ██████╗ ██████╗
|
|
28
|
+
╚══██╔══╝██╔══██╗██╔══██╗████╗ ██║██╔════╝██║ ██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗
|
|
29
|
+
██║ ██████╔╝███████║██╔██╗ ██║███████╗██║ ███████║ ██║ ██║ ██║██████╔╝
|
|
30
|
+
██║ ██╔══██╗██╔══██║██║╚██╗██║╚════██║██║ ██╔══██║ ██║ ██║ ██║██╔══██╗
|
|
31
|
+
██║ ██║ ██║██║ ██║██║ ╚████║███████║███████╗██║ ██║ ██║ ╚██████╔╝██║ ██║
|
|
32
|
+
╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
cli_v2_art = r"""
|
|
36
|
+
██████╗██╗ ██╗ ██╗ ██╗██████╗
|
|
37
|
+
██╔════╝██║ ██║ ██║ ██║╚════██╗
|
|
38
|
+
██║ ██║ ██║ ██║ ██║ █████╔╝
|
|
39
|
+
██║ ██║ ██║ ╚██╗ ██╔╝██╔═══╝
|
|
40
|
+
╚██████╗███████╗██║ ╚████╔╝ ███████╗
|
|
41
|
+
╚═════╝╚══════╝╚═╝ ╚═══╝ ╚══════╝
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
print(f"\n{BOLD}{RED}{error_art.strip()}{RESET}")
|
|
45
|
+
print(f"{BOLD}{BLUE}{translator_art.strip()}{RESET}")
|
|
46
|
+
print(f"{BOLD}{CYAN}{cli_v2_art.strip()}{RESET}")
|
|
47
|
+
print(f"\n{BOLD}{MAGENTA}================================================================================{RESET}")
|
|
48
|
+
print(f"{BOLD}{YELLOW}⚡ Fatal to Fabulous ⚡{RESET}")
|
|
49
|
+
print(f"{BOLD}{WHITE} ⇆ Offline Python Traceback Explainer V2 ⇆ {RESET}")
|
|
50
|
+
print(f"{BOLD}{MAGENTA}================================================================================{RESET}\n")
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
print_install_banner()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command Line Interface (CLI) module for the Error Translator.
|
|
3
|
+
|
|
4
|
+
This module provides the terminal entry point (`explain-error`). It parses arguments,
|
|
5
|
+
handles standard input streams, and orchestrates the translation process.
|
|
6
|
+
"""
|
|
7
|
+
import argparse
|
|
8
|
+
import sys
|
|
9
|
+
from .core import translate_error
|
|
10
|
+
from .ui import print_about, print_help, print_result, print_result_json, VERSION, console
|
|
11
|
+
from .runner import run_script
|
|
12
|
+
|
|
13
|
+
def main():
|
|
14
|
+
"""Main CLI entry point."""
|
|
15
|
+
parser = argparse.ArgumentParser(
|
|
16
|
+
prog="explain-error",
|
|
17
|
+
description="Error Translator — Turn cryptic Python tracebacks into clear, actionable advice.",
|
|
18
|
+
epilog="""
|
|
19
|
+
Examples:
|
|
20
|
+
explain-error run my_script.py
|
|
21
|
+
explain-error "NameError: name 'usr_count' is not defined"
|
|
22
|
+
cat error.log | explain-error
|
|
23
|
+
""",
|
|
24
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
25
|
+
add_help=False
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
parser.add_argument("-a", "--about", action="store_true", help="Display information about the tool.")
|
|
29
|
+
parser.add_argument("-v", "--version", action="store_true", help="Show the current version of the tool.")
|
|
30
|
+
parser.add_argument("--json", action="store_true", dest="as_json", help="Output the translated error as a JSON object.")
|
|
31
|
+
parser.add_argument("-h", "--help", action="store_true", help="Help user through documentation.")
|
|
32
|
+
parser.add_argument("args", nargs="*", help="Positional arguments.")
|
|
33
|
+
|
|
34
|
+
parsed_args = parser.parse_args()
|
|
35
|
+
|
|
36
|
+
# Handle meta-flags
|
|
37
|
+
if parsed_args.about:
|
|
38
|
+
print_about()
|
|
39
|
+
sys.exit(0)
|
|
40
|
+
|
|
41
|
+
if parsed_args.version:
|
|
42
|
+
console.print(f"Error Translator CLI Version: [bold green]{VERSION}[/]")
|
|
43
|
+
sys.exit(0)
|
|
44
|
+
|
|
45
|
+
if parsed_args.help:
|
|
46
|
+
console.print(f"Error Translator CLI Version: [bold green]{VERSION}[/]")
|
|
47
|
+
print_help()
|
|
48
|
+
sys.exit(0)
|
|
49
|
+
|
|
50
|
+
# Choose output strategy
|
|
51
|
+
emit = print_result_json if parsed_args.as_json else print_result
|
|
52
|
+
|
|
53
|
+
# Handle piped input (e.g. `cat error.log | explain-error`)
|
|
54
|
+
if not sys.stdin.isatty():
|
|
55
|
+
error_input = sys.stdin.read()
|
|
56
|
+
if error_input.strip():
|
|
57
|
+
emit(translate_error(error_input))
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
# Provide help if no arguments are passed
|
|
61
|
+
if not parsed_args.args:
|
|
62
|
+
print_help()
|
|
63
|
+
sys.exit(1)
|
|
64
|
+
|
|
65
|
+
# Detect the "run <script.py>" sub-command
|
|
66
|
+
if parsed_args.args[0] == "run" and len(parsed_args.args) > 1:
|
|
67
|
+
script_name = parsed_args.args[1]
|
|
68
|
+
run_script(script_name, as_json=parsed_args.as_json)
|
|
69
|
+
else:
|
|
70
|
+
# Otherwise, treat the entire string of arguments as a raw traceback text
|
|
71
|
+
error_input = " ".join(parsed_args.args)
|
|
72
|
+
emit(translate_error(error_input))
|
|
73
|
+
|
|
74
|
+
if __name__ == "__main__":
|
|
75
|
+
main()
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core translation engine for the Error Translator CLI.
|
|
3
|
+
|
|
4
|
+
This module is responsible for:
|
|
5
|
+
1. Orchestrating error translation.
|
|
6
|
+
2. Using a fast C extension for matching if available, with a Python fallback.
|
|
7
|
+
3. Delegating to AST handlers for deep, code-specific insights.
|
|
8
|
+
"""
|
|
9
|
+
from .ast.ast_handlers import AST_REGISTRY
|
|
10
|
+
from .rules import load_rules, compiled_rules
|
|
11
|
+
from .parser import extract_location, extract_code_context
|
|
12
|
+
|
|
13
|
+
# Attempt to load the ultra-fast C extension for matching rules,
|
|
14
|
+
# and fallback to the Python implementation if it's unavailable.
|
|
15
|
+
try:
|
|
16
|
+
from .fast_matcher import match_loop # type: ignore
|
|
17
|
+
C_EXTENSION_AVAILABLE = True
|
|
18
|
+
except ImportError:
|
|
19
|
+
C_EXTENSION_AVAILABLE = False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def translate_error(traceback_text: str) -> dict:
|
|
23
|
+
"""
|
|
24
|
+
Translate a raw traceback string into a detailed explanation dictionary.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
traceback_text (str): The raw traceback string.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
dict: A dictionary containing the explanation, suggested fix,
|
|
31
|
+
AST-based insight (if any), file, line number, code context,
|
|
32
|
+
and the matched error line.
|
|
33
|
+
"""
|
|
34
|
+
# Load configuration rules and pre-compiled regex patterns
|
|
35
|
+
data = load_rules()
|
|
36
|
+
rules = compiled_rules()
|
|
37
|
+
default_error = data["default"]
|
|
38
|
+
|
|
39
|
+
# Extract non-empty lines from the traceback
|
|
40
|
+
lines = [line.strip() for line in traceback_text.strip().split("\n") if line.strip()]
|
|
41
|
+
if not lines:
|
|
42
|
+
return {"explanation": "No error text provided.", "fix": "Provide a valid Python error."}
|
|
43
|
+
|
|
44
|
+
# The actual error message is typically the last line in a traceback
|
|
45
|
+
actual_error_line = lines[-1]
|
|
46
|
+
|
|
47
|
+
# Extract the origin of the error (file name and line number)
|
|
48
|
+
file_name, line_number = extract_location(traceback_text)
|
|
49
|
+
|
|
50
|
+
# Attempt to read the exact line of code that caused the error
|
|
51
|
+
code_context = extract_code_context(file_name, line_number)
|
|
52
|
+
|
|
53
|
+
# ==========================================
|
|
54
|
+
# FAST MATCHING ENGINE (C Extension + Python Fallback)
|
|
55
|
+
# ==========================================
|
|
56
|
+
match = None
|
|
57
|
+
rule = None
|
|
58
|
+
|
|
59
|
+
if C_EXTENSION_AVAILABLE:
|
|
60
|
+
# Execute the C extension for maximum performance
|
|
61
|
+
result = match_loop(actual_error_line, rules)
|
|
62
|
+
if result:
|
|
63
|
+
match, rule = result
|
|
64
|
+
else:
|
|
65
|
+
# Fallback to standard Python regex loop
|
|
66
|
+
for pattern, r in rules:
|
|
67
|
+
m = pattern.search(actual_error_line)
|
|
68
|
+
if m:
|
|
69
|
+
match, rule = m, r
|
|
70
|
+
break
|
|
71
|
+
|
|
72
|
+
# If we found a matching rule, format the explanation and fix
|
|
73
|
+
if match and rule:
|
|
74
|
+
# Extract variables from the regex groups (e.g., variable names, functions)
|
|
75
|
+
extracted_values = list(match.groups())
|
|
76
|
+
|
|
77
|
+
# Inject the extracted values into the template fix string
|
|
78
|
+
fix_text = rule["fix"].format(*extracted_values)
|
|
79
|
+
|
|
80
|
+
# Parse the error type (e.g., "NameError", "TypeError") to dispatch AST insights
|
|
81
|
+
error_type = actual_error_line.split(":")[0].strip()
|
|
82
|
+
|
|
83
|
+
# Check if there's a specialized AST handler for this specific error type
|
|
84
|
+
handler_function = AST_REGISTRY.get(error_type)
|
|
85
|
+
insight = None
|
|
86
|
+
|
|
87
|
+
# If an AST handler exists and the file is accessible, run deep code analysis
|
|
88
|
+
if handler_function and file_name != "Unknown File":
|
|
89
|
+
insight = handler_function(file_name, line_number, extracted_values)
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
"explanation": rule["explanation"].format(*extracted_values),
|
|
93
|
+
"fix": fix_text,
|
|
94
|
+
"ast_insight": insight,
|
|
95
|
+
"matched_error": actual_error_line,
|
|
96
|
+
"file": file_name,
|
|
97
|
+
"line": line_number,
|
|
98
|
+
"code": code_context,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# Fallback when the error doesn't match any known rules
|
|
102
|
+
return {
|
|
103
|
+
"explanation": default_error["explanation"],
|
|
104
|
+
"fix": default_error["fix"],
|
|
105
|
+
"matched_error": actual_error_line,
|
|
106
|
+
"file": file_name,
|
|
107
|
+
"line": line_number,
|
|
108
|
+
"code": code_context,
|
|
109
|
+
}
|