letx 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.
- letx/__init__.py +23 -0
- letx/cli.py +69 -0
- letx/core/__init__.py +26 -0
- letx/core/base.py +60 -0
- letx/modules/__init__.py +30 -0
- letx/modules/debug.py +294 -0
- letx/modules/fix.py +212 -0
- letx/utils/__init__.py +51 -0
- letx/utils/printer.py +68 -0
- letx/utils/runner.py +76 -0
- letx-0.1.0.dist-info/METADATA +166 -0
- letx-0.1.0.dist-info/RECORD +16 -0
- letx-0.1.0.dist-info/WHEEL +5 -0
- letx-0.1.0.dist-info/entry_points.txt +4 -0
- letx-0.1.0.dist-info/licenses/LICENSE +674 -0
- letx-0.1.0.dist-info/top_level.txt +1 -0
letx/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
letx - The developer utility toolkit
|
|
3
|
+
Copyright (C) 2026 <Your Name or Organization>
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
-------------------------------------------------------------------------
|
|
19
|
+
letx — The developer utility toolkit.
|
|
20
|
+
Debug smarter, fix faster.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
__version__ = "0.1.0"
|
letx/cli.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
letx.cli
|
|
3
|
+
--------
|
|
4
|
+
Shared CLI dispatcher — each entry_point calls run_module(name).
|
|
5
|
+
Copyright (C) 2026 AMMAAR BAKSHI
|
|
6
|
+
|
|
7
|
+
This program is free software: you can redistribute it and/or modify
|
|
8
|
+
it under the terms of the GNU General Public License as published by
|
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
(at your option) any later version.
|
|
11
|
+
|
|
12
|
+
This program is distributed in the hope that it will be useful,
|
|
13
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
GNU General Public License for more details.
|
|
16
|
+
|
|
17
|
+
You should have received a copy of the GNU General Public License
|
|
18
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>6
|
|
19
|
+
"""
|
|
20
|
+
import sys
|
|
21
|
+
from argparse import ArgumentParser
|
|
22
|
+
|
|
23
|
+
from letx.modules import REGISTRY
|
|
24
|
+
from letx.utils.printer import console, error
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def run_module(module_name: str):
|
|
28
|
+
"""Generic dispatcher. Called by each console_scripts entry point."""
|
|
29
|
+
module = REGISTRY.get(module_name)
|
|
30
|
+
if module is None:
|
|
31
|
+
error(f"Unknown module: [bold]{module_name}[/bold]")
|
|
32
|
+
sys.exit(1)
|
|
33
|
+
|
|
34
|
+
parser = ArgumentParser(
|
|
35
|
+
prog=module.name,
|
|
36
|
+
description=module.description,
|
|
37
|
+
)
|
|
38
|
+
module.register_args(parser)
|
|
39
|
+
|
|
40
|
+
args = parser.parse_args()
|
|
41
|
+
sys.exit(module.run(args))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ── Individual entry points ───────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
def letx_debug():
|
|
47
|
+
run_module("letxDebug")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def letx_fix():
|
|
51
|
+
run_module("letxFix")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ── Global `letx` meta-command ────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
def letx_main():
|
|
57
|
+
"""
|
|
58
|
+
`letx` — lists all available tools and their descriptions.
|
|
59
|
+
"""
|
|
60
|
+
from rich.table import Table
|
|
61
|
+
e = Table(box=box.ROUNDED, border_style="yellow", show_header=True)
|
|
62
|
+
table.add_column("Command", style="bold cyan", no_wrap=True)
|
|
63
|
+
table.add_column("Description", style="white")
|
|
64
|
+
|
|
65
|
+
for name, mod in REGISTRY.items():
|
|
66
|
+
table.add_row(name, mod.description)
|
|
67
|
+
|
|
68
|
+
console.print(table)
|
|
69
|
+
console.print("\n[dim]Run [bold]<command> --help[/bold] for usage details.[/dim]\n");
|
letx/core/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
letx - The developer utility toolkit
|
|
3
|
+
Copyright (C) 2026 AMMAAR BAKSHI
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
-------------------------------------------------------------------------
|
|
19
|
+
letx.core
|
|
20
|
+
---------
|
|
21
|
+
Core abstract base classes and logic for the letx toolkit.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from .base import LetxModule
|
|
25
|
+
|
|
26
|
+
__all__ = ["LetxModule"]
|
letx/core/base.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
letx.core.base
|
|
3
|
+
--------------
|
|
4
|
+
Base class for all letx modules.
|
|
5
|
+
Every new module (letxDebug, letxFix, letxLint, etc.) subclasses LetxModule.
|
|
6
|
+
|
|
7
|
+
Copyright (C) 2026 AMMAAR BAKSHI
|
|
8
|
+
|
|
9
|
+
This program is free software: you can redistribute it and/or modify
|
|
10
|
+
it under the terms of the GNU General Public License as published by
|
|
11
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
12
|
+
(at your option) any later version.
|
|
13
|
+
|
|
14
|
+
This program is distributed in the hope that it will be useful,
|
|
15
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
17
|
+
GNU General Public License for more details.
|
|
18
|
+
|
|
19
|
+
You should have received a copy of the GNU General Public License
|
|
20
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
21
|
+
"""
|
|
22
|
+
from abc import ABC, abstractmethod
|
|
23
|
+
from argparse import ArgumentParser
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class LetxModule(ABC):
|
|
27
|
+
"""
|
|
28
|
+
Base class for all letx CLI modules.
|
|
29
|
+
|
|
30
|
+
To add a new module:
|
|
31
|
+
1. Create a file in letx/modules/
|
|
32
|
+
2. Subclass LetxModule
|
|
33
|
+
3. Implement name, description, register_args, run
|
|
34
|
+
4. Register it in letx/modules/__init__.py
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def name(self) -> str:
|
|
40
|
+
"""CLI command name, e.g. 'letxDebug'"""
|
|
41
|
+
...
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
@abstractmethod
|
|
45
|
+
def description(self) -> str:
|
|
46
|
+
"""Short one-liner shown in --help"""
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def register_args(self, parser: ArgumentParser) -> None:
|
|
51
|
+
"""Add module-specific arguments to the parser."""
|
|
52
|
+
...
|
|
53
|
+
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def run(self, args) -> int:
|
|
56
|
+
"""
|
|
57
|
+
Execute the module logic.
|
|
58
|
+
Return 0 on success, non-zero on failure.
|
|
59
|
+
"""
|
|
60
|
+
...
|
letx/modules/__init__.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
letx - The developer utility toolkit
|
|
3
|
+
Copyright (C) 2026 AMMAAR BAKSHI
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
-------------------------------------------------------------------------
|
|
19
|
+
letx.modules
|
|
20
|
+
------------
|
|
21
|
+
Registry for all available letx modules.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from letx.modules.debug import DebugModule
|
|
25
|
+
from letx.modules.fix import FixModule
|
|
26
|
+
|
|
27
|
+
REGISTRY = {
|
|
28
|
+
"letxDebug": DebugModule(),
|
|
29
|
+
"letxFix": FixModule(),
|
|
30
|
+
}
|
letx/modules/debug.py
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""
|
|
2
|
+
letx.modules.debug
|
|
3
|
+
------------------
|
|
4
|
+
letxDebug — Smart Python debugger.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
letxDebug file.py # run and show clean debug output
|
|
8
|
+
letxDebug -e file.py # explain the error in plain English
|
|
9
|
+
letxDebug -s file.py # suggest a fix/solution
|
|
10
|
+
letxDebug -a file.py # explain + solution (all)
|
|
11
|
+
|
|
12
|
+
Copyright (C) 2026 AMMAAR BAKSHI
|
|
13
|
+
|
|
14
|
+
This program is free software: you can redistribute it and/or modify
|
|
15
|
+
it under the terms of the GNU General Public License as published by
|
|
16
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
17
|
+
(at your option) any later version.
|
|
18
|
+
|
|
19
|
+
This program is distributed in the hope that it will be useful,
|
|
20
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
21
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
22
|
+
GNU General Public License for more details.
|
|
23
|
+
|
|
24
|
+
You should have received a copy of the GNU General Public License
|
|
25
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
26
|
+
"""
|
|
27
|
+
import os
|
|
28
|
+
from argparse import ArgumentParser
|
|
29
|
+
|
|
30
|
+
from rich.panel import Panel
|
|
31
|
+
from rich.text import Text
|
|
32
|
+
from rich import box
|
|
33
|
+
|
|
34
|
+
from letx.core.base import LetxModule
|
|
35
|
+
from letx.utils.runner import run_file
|
|
36
|
+
from letx.utils.printer import (
|
|
37
|
+
console, header, section, success, error,
|
|
38
|
+
warning, info, code_block, divider, tip
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# ── Known error explanations ────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
KNOWN_ERRORS = {
|
|
44
|
+
"SyntaxError": (
|
|
45
|
+
"Python couldn't understand your code because of a typo or missing character.",
|
|
46
|
+
[
|
|
47
|
+
"Check for missing colons `:` after `if`, `for`, `def`, `class`",
|
|
48
|
+
"Check for unclosed brackets `(`, `[`, `{`",
|
|
49
|
+
"Check for mismatched quotes `'` or `\"`",
|
|
50
|
+
],
|
|
51
|
+
),
|
|
52
|
+
"IndentationError": (
|
|
53
|
+
"Your code's indentation (spaces/tabs) is inconsistent.",
|
|
54
|
+
[
|
|
55
|
+
"Make sure you use only spaces OR only tabs (not both)",
|
|
56
|
+
"Each block inside `if`, `for`, `def` must be indented one level",
|
|
57
|
+
"Don't add extra spaces accidentally",
|
|
58
|
+
],
|
|
59
|
+
),
|
|
60
|
+
"NameError": (
|
|
61
|
+
"You used a variable or function that Python doesn't know about yet.",
|
|
62
|
+
[
|
|
63
|
+
"Check the spelling of the variable name",
|
|
64
|
+
"Make sure you defined the variable before using it",
|
|
65
|
+
"If it's a function from a module, did you import it?",
|
|
66
|
+
],
|
|
67
|
+
),
|
|
68
|
+
"TypeError": (
|
|
69
|
+
"You performed an operation on the wrong type of value.",
|
|
70
|
+
[
|
|
71
|
+
"e.g. adding a string and a number: use `int()` or `str()` to convert",
|
|
72
|
+
"Check what type your function expects vs what you're passing",
|
|
73
|
+
"Use `type(x)` to inspect a variable's type",
|
|
74
|
+
],
|
|
75
|
+
),
|
|
76
|
+
"ValueError": (
|
|
77
|
+
"You passed the right type but an invalid value.",
|
|
78
|
+
[
|
|
79
|
+
"e.g. `int('hello')` — 'hello' can't become a number",
|
|
80
|
+
"Check the input data before converting or processing it",
|
|
81
|
+
],
|
|
82
|
+
),
|
|
83
|
+
"IndexError": (
|
|
84
|
+
"You tried to access a list position that doesn't exist.",
|
|
85
|
+
[
|
|
86
|
+
"Lists start at index 0, not 1",
|
|
87
|
+
"Check the length: `len(my_list)` before accessing `my_list[i]`",
|
|
88
|
+
"The last valid index is `len(my_list) - 1`",
|
|
89
|
+
],
|
|
90
|
+
),
|
|
91
|
+
"KeyError": (
|
|
92
|
+
"You tried to access a dictionary key that doesn't exist.",
|
|
93
|
+
[
|
|
94
|
+
"Use `dict.get('key', default)` instead of `dict['key']` to avoid crashes",
|
|
95
|
+
"Check if the key exists first: `if 'key' in my_dict:`",
|
|
96
|
+
"Print `my_dict.keys()` to see what keys are available",
|
|
97
|
+
],
|
|
98
|
+
),
|
|
99
|
+
"AttributeError": (
|
|
100
|
+
"You tried to use a method or property that doesn't exist on that object.",
|
|
101
|
+
[
|
|
102
|
+
"Check the spelling of the attribute/method",
|
|
103
|
+
"Use `dir(obj)` to list what methods an object has",
|
|
104
|
+
"Make sure the object is the type you think it is",
|
|
105
|
+
],
|
|
106
|
+
),
|
|
107
|
+
"ImportError": (
|
|
108
|
+
"Python couldn't find or load the module you're importing.",
|
|
109
|
+
[
|
|
110
|
+
"Run `pip install <module-name>` to install missing packages",
|
|
111
|
+
"Check the spelling of the module name",
|
|
112
|
+
"Make sure you're in the right virtual environment",
|
|
113
|
+
],
|
|
114
|
+
),
|
|
115
|
+
"ModuleNotFoundError": (
|
|
116
|
+
"The module you're trying to import is not installed.",
|
|
117
|
+
[
|
|
118
|
+
"Run: `pip install <module-name>`",
|
|
119
|
+
"Check if the module name is spelled correctly",
|
|
120
|
+
"Verify your Python environment has the package",
|
|
121
|
+
],
|
|
122
|
+
),
|
|
123
|
+
"ZeroDivisionError": (
|
|
124
|
+
"Your code tried to divide a number by zero.",
|
|
125
|
+
[
|
|
126
|
+
"Add a check before dividing: `if denominator != 0:`",
|
|
127
|
+
"Use a try/except block to handle it gracefully",
|
|
128
|
+
],
|
|
129
|
+
),
|
|
130
|
+
"FileNotFoundError": (
|
|
131
|
+
"Python looked for a file but couldn't find it at the given path.",
|
|
132
|
+
[
|
|
133
|
+
"Double-check the file path and spelling",
|
|
134
|
+
"Use `os.path.exists(path)` to verify the file is there",
|
|
135
|
+
"Make sure you're running from the right directory",
|
|
136
|
+
],
|
|
137
|
+
),
|
|
138
|
+
"RecursionError": (
|
|
139
|
+
"Your function called itself too many times — an infinite loop of calls.",
|
|
140
|
+
[
|
|
141
|
+
"Make sure your recursive function has a clear base case that stops the recursion",
|
|
142
|
+
"Check if the base case is actually being reached",
|
|
143
|
+
],
|
|
144
|
+
),
|
|
145
|
+
"MemoryError": (
|
|
146
|
+
"Python ran out of memory trying to run your code.",
|
|
147
|
+
[
|
|
148
|
+
"Avoid loading huge files all at once — read in chunks",
|
|
149
|
+
"Delete large variables with `del x` when no longer needed",
|
|
150
|
+
"Use generators instead of lists for large data",
|
|
151
|
+
],
|
|
152
|
+
),
|
|
153
|
+
"OverflowError": (
|
|
154
|
+
"A numeric operation produced a result too large for Python to handle.",
|
|
155
|
+
[
|
|
156
|
+
"Use Python's `decimal` module for very large precise numbers",
|
|
157
|
+
"Check your algorithm for unintended exponential growth",
|
|
158
|
+
],
|
|
159
|
+
),
|
|
160
|
+
"StopIteration": (
|
|
161
|
+
"You called `next()` on an iterator that ran out of items.",
|
|
162
|
+
[
|
|
163
|
+
"Use a `for` loop instead of manually calling `next()`",
|
|
164
|
+
"Wrap in try/except StopIteration if you need to handle the end explicitly",
|
|
165
|
+
],
|
|
166
|
+
),
|
|
167
|
+
"RuntimeError": (
|
|
168
|
+
"A general error occurred during execution.",
|
|
169
|
+
[
|
|
170
|
+
"Read the full traceback carefully for the specific cause",
|
|
171
|
+
"Common in async code — make sure you're using `await` correctly",
|
|
172
|
+
],
|
|
173
|
+
),
|
|
174
|
+
"PermissionError": (
|
|
175
|
+
"Your script doesn't have permission to access a file or resource.",
|
|
176
|
+
[
|
|
177
|
+
"Check if the file is read-only",
|
|
178
|
+
"Try running with elevated permissions if appropriate",
|
|
179
|
+
"Ensure the file isn't open in another program",
|
|
180
|
+
],
|
|
181
|
+
),
|
|
182
|
+
"TimeoutError": (
|
|
183
|
+
"An operation took too long and was cancelled.",
|
|
184
|
+
[
|
|
185
|
+
"Add timeout handling with `try/except TimeoutError`",
|
|
186
|
+
"Check network or I/O calls that might be hanging",
|
|
187
|
+
],
|
|
188
|
+
),
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
# ── Module ───────────────────────────────────────────────────────────────────
|
|
192
|
+
|
|
193
|
+
class DebugModule(LetxModule):
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def name(self) -> str:
|
|
197
|
+
return "letxDebug"
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def description(self) -> str:
|
|
201
|
+
return "Smart Python debugger — run, explain, and fix errors"
|
|
202
|
+
|
|
203
|
+
def register_args(self, parser: ArgumentParser) -> None:
|
|
204
|
+
parser.add_argument("file", help="Python file to debug")
|
|
205
|
+
mode = parser.add_mutually_exclusive_group()
|
|
206
|
+
mode.add_argument("-e", "--explain", action="store_true",
|
|
207
|
+
help="Explain the error in plain English")
|
|
208
|
+
mode.add_argument("-s", "--solution", action="store_true",
|
|
209
|
+
help="Suggest a solution/fix")
|
|
210
|
+
mode.add_argument("-a", "--all", dest="all_modes", action="store_true",
|
|
211
|
+
help="Explain + solution (everything)")
|
|
212
|
+
|
|
213
|
+
def run(self, args) -> int:
|
|
214
|
+
filepath = args.file
|
|
215
|
+
|
|
216
|
+
if not os.path.isfile(filepath):
|
|
217
|
+
error(f"File not found: [bold]{filepath}[/bold]")
|
|
218
|
+
return 1
|
|
219
|
+
|
|
220
|
+
header("letxDebug", f"Debugging → {filepath}")
|
|
221
|
+
|
|
222
|
+
result = run_file(filepath)
|
|
223
|
+
|
|
224
|
+
# ── Success path ────────────────────────────────────────────────
|
|
225
|
+
if not result.has_error:
|
|
226
|
+
success(f"File ran successfully with exit code [bold green]0[/bold green]")
|
|
227
|
+
if result.stdout:
|
|
228
|
+
section("Output", "📤")
|
|
229
|
+
console.print(result.stdout)
|
|
230
|
+
return 0
|
|
231
|
+
|
|
232
|
+
# ── Base debug output (always shown) ────────────────────────────
|
|
233
|
+
section("Traceback", "🔴")
|
|
234
|
+
console.print(f"[dim]{result.traceback}[/dim]")
|
|
235
|
+
divider()
|
|
236
|
+
|
|
237
|
+
if result.error_type and result.error_message:
|
|
238
|
+
console.print(
|
|
239
|
+
f"[bold red]{result.error_type}[/bold red]: [white]{result.error_message}[/white]"
|
|
240
|
+
)
|
|
241
|
+
if result.error_line:
|
|
242
|
+
warning(f"Error on line [bold]{result.error_line}[/bold] of [bold]{filepath}[/bold]")
|
|
243
|
+
|
|
244
|
+
# Show the relevant source lines
|
|
245
|
+
try:
|
|
246
|
+
with open(filepath) as f:
|
|
247
|
+
lines = f.readlines()
|
|
248
|
+
start = max(0, result.error_line - 3)
|
|
249
|
+
end = min(len(lines), result.error_line + 2)
|
|
250
|
+
snippet = "".join(lines[start:end])
|
|
251
|
+
section("Source Context", "📄")
|
|
252
|
+
code_block(snippet)
|
|
253
|
+
except Exception:
|
|
254
|
+
pass
|
|
255
|
+
|
|
256
|
+
# ── Explain mode ────────────────────────────────────────────────
|
|
257
|
+
if args.explain or args.all_modes:
|
|
258
|
+
self._explain(result)
|
|
259
|
+
|
|
260
|
+
# ── Solution mode ───────────────────────────────────────────────
|
|
261
|
+
if args.solution or args.all_modes:
|
|
262
|
+
self._solution(result)
|
|
263
|
+
|
|
264
|
+
return 1
|
|
265
|
+
|
|
266
|
+
def _explain(self, result):
|
|
267
|
+
section("Error Explanation", "💬")
|
|
268
|
+
etype = result.error_type or "UnknownError"
|
|
269
|
+
entry = KNOWN_ERRORS.get(etype)
|
|
270
|
+
|
|
271
|
+
if entry:
|
|
272
|
+
explanation, _ = entry
|
|
273
|
+
console.print(f"[white]{explanation}[/white]")
|
|
274
|
+
else:
|
|
275
|
+
console.print(
|
|
276
|
+
f"[white]A [bold red]{etype}[/bold red] occurred.\n"
|
|
277
|
+
f"Message: [italic]{result.error_message}[/italic][/white]"
|
|
278
|
+
)
|
|
279
|
+
info("This is an uncommon error — read the traceback carefully.")
|
|
280
|
+
|
|
281
|
+
def _solution(self, result):
|
|
282
|
+
section("Suggested Fix", "🔧")
|
|
283
|
+
etype = result.error_type or "UnknownError"
|
|
284
|
+
entry = KNOWN_ERRORS.get(etype)
|
|
285
|
+
|
|
286
|
+
if entry:
|
|
287
|
+
_, hints = entry
|
|
288
|
+
for i, hint in enumerate(hints, 1):
|
|
289
|
+
console.print(f" [bold cyan]{i}.[/bold cyan] {hint}")
|
|
290
|
+
else:
|
|
291
|
+
tip(f"Search for `{etype}: {result.error_message}` on Stack Overflow or the Python docs.")
|
|
292
|
+
|
|
293
|
+
divider()
|
|
294
|
+
info("Run [bold yellow]letxDebug -a file.py[/bold yellow] to see both explanation and solution.")
|