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 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
+ ...
@@ -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.")