replbase 0.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.
replbase-0.0.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 jmbski
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.1
2
+ Name: replbase
3
+ Version: 0.0.2
4
+ Summary: "Combination of other REPL tools into a reusable class that generates a REPL"
5
+ License: MIT
6
+ Author: Joseph Bochinski
7
+ Author-email: stirgejr@gmail.com
8
+ Requires-Python: >=3.12,<4.0
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Requires-Dist: argcomplete (>=3.5.2,<4.0.0)
13
+ Requires-Dist: prompt-toolkit (>=3.0.48,<4.0.0)
14
+ Requires-Dist: ptpython (>=3.0.29,<4.0.0)
15
+ Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
16
+ Requires-Dist: rich (>=13.9.4,<14.0.0)
17
+ Description-Content-Type: text/markdown
18
+
19
+ # replbase
20
+ A Python tool that combines some features from other REPL packages into one neat reusable base class
21
+
@@ -0,0 +1,2 @@
1
+ # replbase
2
+ A Python tool that combines some features from other REPL packages into one neat reusable base class
@@ -0,0 +1,19 @@
1
+ [build-system]
2
+ requires = [ "poetry-core",]
3
+ build-backend = "poetry.core.masonry.api"
4
+
5
+ [tool.poetry]
6
+ name = "replbase"
7
+ version = "0.0.2"
8
+ description = "\"Combination of other REPL tools into a reusable class that generates a REPL\""
9
+ authors = [ "Joseph Bochinski <stirgejr@gmail.com>",]
10
+ license = "MIT"
11
+ readme = "README.md"
12
+
13
+ [tool.poetry.dependencies]
14
+ python = "^3.12"
15
+ ptpython = "^3.0.29"
16
+ rich = "^13.9.4"
17
+ prompt-toolkit = "^3.0.48"
18
+ pyyaml = "^6.0.2"
19
+ argcomplete = "^3.5.2"
@@ -0,0 +1,5 @@
1
+ """ Package for the ReplBase project. """
2
+
3
+ from replbase.repl_base import ReplBase
4
+
5
+ __all__ = ["ReplBase"]
@@ -0,0 +1,310 @@
1
+ """
2
+
3
+ #######################################################################
4
+
5
+ Module Name: repl_base
6
+ Description: Base class for REPL tools
7
+ Author: Joseph Bochinski
8
+ Date: 2024-12-16
9
+
10
+
11
+ #######################################################################
12
+ """
13
+
14
+ # region Imports
15
+
16
+ import argparse
17
+ import os
18
+ import shlex
19
+
20
+ from dataclasses import dataclass
21
+ from typing import Callable, Literal
22
+
23
+ from prompt_toolkit import PromptSession
24
+ from prompt_toolkit.history import FileHistory
25
+ from prompt_toolkit.completion import WordCompleter
26
+ from prompt_toolkit.styles import Style
27
+ from ptpython.repl import embed
28
+ from rich.console import Console
29
+
30
+ # endregion Imports
31
+
32
+
33
+ # region Constants
34
+
35
+ ColorSystem = Literal["auto", "standard", "256", "truecolor", "windows"]
36
+ # endregion Constants
37
+
38
+
39
+ # region Classes
40
+
41
+
42
+ @dataclass
43
+ class ReplCommand:
44
+ """Class definition for a provided CLI REPL command"""
45
+
46
+ command: Callable = None
47
+ help_txt: str = ""
48
+ parser: argparse.ArgumentParser = None
49
+
50
+
51
+ @dataclass
52
+ class ReplBase:
53
+ """Dataclass for CLI options"""
54
+
55
+ debug_enabled: bool = None
56
+ """Debug mode enabled"""
57
+
58
+ title: str = None
59
+ """Title of the CLI REPL Prompt"""
60
+
61
+ exit_keywords: list[str] = None # field(default_factory=list)
62
+ """List of strings that cause the REPL to close, defaults to x, q,
63
+ exit, and quit"""
64
+
65
+ init_prompt: str | list[str] = None
66
+ """Prompt to display at startup"""
67
+
68
+ color_system: ColorSystem = None
69
+ """Color syste for the rich console"""
70
+
71
+ console: Console = None
72
+ """ Rich Console instance """
73
+
74
+ history: str = None
75
+ """Path to the prompt history file"""
76
+
77
+ temp_file: str = None
78
+ """Path to prompt temporary file"""
79
+
80
+ style: dict | Style = None # field(default_factory=dict)
81
+ """Style for the prompt"""
82
+
83
+ ignore_case: bool = None
84
+ """Ignore case setting for the WordCompleter instance"""
85
+
86
+ commands: dict[str, ReplCommand] = None # field(default_factory=dict)
87
+ """Command dictionary for prompt_toolkit. Keys are command names,
88
+ values are the corresponding description/help text"""
89
+
90
+ def __post_init__(self) -> None:
91
+ if isinstance(self.commands, dict):
92
+ for cmd_name, cmd in self.commands.items():
93
+ if isinstance(cmd, dict):
94
+ self.commands[cmd_name] = ReplCommand(**cmd)
95
+
96
+ if self.debug_enabled is None:
97
+ self.debug_enabled = False
98
+
99
+ self.title = self.title or "CLI Tool"
100
+
101
+ self.exit_keywords = self.exit_keywords or ["x", "q", "exit", "quit"]
102
+
103
+ exit_kw_str = ", ".join(
104
+ f'[bold green]"{kw}"[/bold green]' for kw in self.exit_keywords
105
+ )
106
+ exit_kw_pref = "Type one of " if len(self.exit_keywords) > 1 else "Type "
107
+ exit_str = f"[cyan]{exit_kw_pref}{exit_kw_str} to exit[/cyan]"
108
+
109
+ self.init_prompt = self.init_prompt or [
110
+ f"[bold cyan]<<| {self.title} |>>[/bold cyan]",
111
+ exit_str,
112
+ '[cyan]Type [bold green]"help"[/bold green] to view available commands.',
113
+ ]
114
+
115
+ self.color_system = self.color_system or "truecolor"
116
+
117
+ self.console = Console(color_system=self.color_system)
118
+
119
+ self.history = self.history or os.path.expanduser(
120
+ "~/.config/.prompt_history"
121
+ )
122
+
123
+ if self.history:
124
+ hist_dir = os.path.dirname(self.history)
125
+ if not os.path.exists(hist_dir):
126
+ os.makedirs(hist_dir, exist_ok=True)
127
+
128
+ self.temp_file = self.temp_file or os.path.expanduser(
129
+ "~/.config/.prompt_tmp"
130
+ )
131
+
132
+ if self.temp_file:
133
+ temp_dir = os.path.dirname(self.temp_file)
134
+ if not os.path.exists(temp_dir):
135
+ os.makedirs(temp_dir, exist_ok=True)
136
+
137
+ self.style = self.style or {
138
+ "prompt": "bold green",
139
+ "": "default",
140
+ }
141
+ if isinstance(self.style, dict):
142
+ self.style = Style.from_dict(self.style)
143
+
144
+ self.apply_def_cmds()
145
+
146
+ def apply_def_cmds(self) -> None:
147
+ """Add the descriptions for the help and exit commands"""
148
+ base_cmds: dict[str, ReplCommand] = {
149
+ "\\[help, h]": ReplCommand(help_txt="Display this help message again"),
150
+ }
151
+ if self.exit_keywords:
152
+ exit_str = ", ".join(self.exit_keywords)
153
+ base_cmds.update(
154
+ {
155
+ f"\\[{exit_str}]": ReplCommand(help_txt="Exit tool"),
156
+ }
157
+ )
158
+
159
+ self.commands = self.commands or {}
160
+ base_cmds.update(self.commands)
161
+ self.commands = base_cmds
162
+
163
+ def get_cmd_names(self) -> list[str]:
164
+ """Retrieve names of commands, parsing out the help/exit commands"""
165
+
166
+ help_str = "[help, h]"
167
+ exit_str = f'[{", ".join(self.exit_keywords)}]'
168
+ names: list[str] = [
169
+ name
170
+ for name in self.commands.keys()
171
+ if name not in [help_str, exit_str]
172
+ ]
173
+ names.extend(["help", "h"])
174
+ names.extend(self.exit_keywords)
175
+ return names
176
+
177
+ def print(self, *args) -> None:
178
+ """Shortcut to console.print"""
179
+ self.console.print(*args)
180
+
181
+ def input(self, *args) -> str:
182
+ """Shortcut to console.input"""
183
+ return self.console.input(*args)
184
+
185
+ def debug(self, *args) -> None:
186
+ """Print only if debug_enabled == True"""
187
+ if self.debug_enabled:
188
+ self.print(*args)
189
+
190
+ def add_command(
191
+ self,
192
+ cmd_name: str,
193
+ cmd_func: Callable = None,
194
+ help_txt: str = "",
195
+ use_parser: bool = False,
196
+ description: str = "",
197
+ ) -> ReplCommand:
198
+ """Add a command to the REPL
199
+
200
+ Args:
201
+ cmd_name (str): Name of the command
202
+ cmd_func (Callable, optional): Function to execute when called.
203
+ Defaults to None.
204
+ help_txt (str, optional): Help text to display from REPL help command.
205
+ Defaults to "".
206
+ use_parser (bool, optional): If true, adds an argparse.ArgumentParser
207
+ to the new ReplCommand instance. Defaults to False.
208
+ description (str, optional): Optional description for the
209
+ ArgumentParser help text. Defaults to help_txt.
210
+
211
+ Returns:
212
+ ReplCommand: The new ReplCommand instance
213
+ """
214
+
215
+ new_cmd = ReplCommand(command=cmd_func, help_txt=help_txt)
216
+ if use_parser:
217
+ new_cmd.parser = argparse.ArgumentParser(
218
+ description=description or help_txt
219
+ )
220
+ self.commands[cmd_name] = new_cmd
221
+ return new_cmd
222
+
223
+ def interactive(self, *args, **kwargs) -> None:
224
+ """Starts an interactive session from within the class"""
225
+ if kwargs:
226
+ globals().update(kwargs)
227
+ embed(globals(), locals())
228
+
229
+ def show_help(self) -> None:
230
+ """Print out the provided help text"""
231
+
232
+ for cmd_name, cmd in self.commands.items():
233
+ self.print(
234
+ f"[bold green]{cmd_name}:[/bold green] [cyan]{cmd.help_txt}[/cyan]"
235
+ )
236
+
237
+ def run(self) -> None:
238
+ """Initiates a REPL with the provided configuration"""
239
+
240
+ completer = WordCompleter(
241
+ self.get_cmd_names(), ignore_case=self.ignore_case
242
+ )
243
+
244
+ session = PromptSession(
245
+ completer=completer,
246
+ style=self.style,
247
+ history=FileHistory(self.history),
248
+ tempfile=self.temp_file,
249
+ )
250
+
251
+ if isinstance(self.init_prompt, list):
252
+ for line in self.init_prompt:
253
+ self.print(line)
254
+ else:
255
+ self.print(self.init_prompt)
256
+
257
+ while True:
258
+ try:
259
+ user_input = session.prompt("> ", complete_while_typing=True)
260
+
261
+ if user_input.lower() in ["help", "h"]:
262
+ self.show_help()
263
+ elif user_input.lower() in self.exit_keywords:
264
+ break
265
+ else:
266
+ args = shlex.split(user_input)
267
+ if not args:
268
+ self.print(
269
+ "[bold yellow][WARNING]: No command provided[/bold yellow]"
270
+ )
271
+ continue
272
+
273
+ cmd = self.commands.get(args[0])
274
+
275
+ if not cmd:
276
+ self.print(
277
+ "[bold yellow][WARNING]: Invalid command[/bold yellow]"
278
+ )
279
+ continue
280
+
281
+ cmd_args = args[1:]
282
+ if cmd.command:
283
+ if cmd.parser:
284
+ if cmd_args and cmd_args[0] in [
285
+ "help",
286
+ "h",
287
+ "-h",
288
+ "--help",
289
+ ]:
290
+ cmd.parser.print_help()
291
+ continue
292
+
293
+ cmd.command(cmd.parser.parse_args(cmd_args))
294
+ else:
295
+ cmd.command(*cmd_args)
296
+ else:
297
+ self.print(
298
+ "[bold yellow][WARNING]: No function provided for command[/bold yellow]"
299
+ )
300
+ except (EOFError, KeyboardInterrupt):
301
+ self.print("[bold yellow]Exiting REPL...[/bold yellow]")
302
+ break
303
+
304
+
305
+ # endregion Classes
306
+
307
+
308
+ # region Functions
309
+
310
+ # endregion Functions