cli-ih 0.6.1.2__tar.gz → 0.6.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.
- {cli_ih-0.6.1.2 → cli_ih-0.6.2}/PKG-INFO +1 -1
- {cli_ih-0.6.1.2 → cli_ih-0.6.2}/cli_ih/__init__.py +2 -1
- cli_ih-0.6.2/cli_ih/asyncClient.py +169 -0
- cli_ih-0.6.1.2/cli_ih/Input_Handler.py → cli_ih-0.6.2/cli_ih/client.py +5 -9
- cli_ih-0.6.2/cli_ih/exceptions.py +4 -0
- {cli_ih-0.6.1.2 → cli_ih-0.6.2}/cli_ih.egg-info/PKG-INFO +1 -1
- {cli_ih-0.6.1.2 → cli_ih-0.6.2}/cli_ih.egg-info/SOURCES.txt +3 -1
- {cli_ih-0.6.1.2 → cli_ih-0.6.2}/cli_ih.egg-info/top_level.txt +0 -1
- {cli_ih-0.6.1.2 → cli_ih-0.6.2}/pyproject.toml +1 -1
- {cli_ih-0.6.1.2 → cli_ih-0.6.2}/README.md +0 -0
- {cli_ih-0.6.1.2 → cli_ih-0.6.2}/cli_ih.egg-info/dependency_links.txt +0 -0
- {cli_ih-0.6.1.2 → cli_ih-0.6.2}/setup.cfg +0 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
from .exceptions import HandlerClosed
|
|
3
|
+
import logging, warnings, asyncio, inspect
|
|
4
|
+
|
|
5
|
+
class AsyncInputHandler:
|
|
6
|
+
def __init__(self, cursor = "", *, logger: logging.Logger | None = None, register_defaults: bool = True):
|
|
7
|
+
self.commands = {}
|
|
8
|
+
self.is_running = False
|
|
9
|
+
self.cursor = f"{cursor.strip()} "
|
|
10
|
+
self.global_logger = logger if logger else None
|
|
11
|
+
self.logger = logger.getChild("InputHandler") if logger else None
|
|
12
|
+
self.register_defaults = register_defaults
|
|
13
|
+
if self.register_defaults:
|
|
14
|
+
self.register_default_commands()
|
|
15
|
+
else:
|
|
16
|
+
self.__warning("The default commands are disabled in the current instance.")
|
|
17
|
+
|
|
18
|
+
def get_logger(self):
|
|
19
|
+
return self.logger
|
|
20
|
+
|
|
21
|
+
def __debug(self, msg: str):
|
|
22
|
+
if self.logger:
|
|
23
|
+
self.logger.debug(msg)
|
|
24
|
+
else:
|
|
25
|
+
print(f"[DEBUG]: {msg}")
|
|
26
|
+
|
|
27
|
+
def __info(self, msg: str):
|
|
28
|
+
if self.logger:
|
|
29
|
+
self.logger.info(msg)
|
|
30
|
+
else:
|
|
31
|
+
print(f"[INFO]: {msg}")
|
|
32
|
+
|
|
33
|
+
def __warning(self, msg: str):
|
|
34
|
+
if self.logger:
|
|
35
|
+
self.logger.warning(msg)
|
|
36
|
+
else:
|
|
37
|
+
print(f"[WARNING]: {msg}")
|
|
38
|
+
|
|
39
|
+
def __error(self, msg: str):
|
|
40
|
+
if self.logger:
|
|
41
|
+
self.logger.error(msg)
|
|
42
|
+
else:
|
|
43
|
+
print(f"[ERROR]: {msg}")
|
|
44
|
+
|
|
45
|
+
def __exeption(self, msg: str, e: Exception):
|
|
46
|
+
if self.logger:
|
|
47
|
+
self.logger.exception(f"{msg}: {e}")
|
|
48
|
+
else:
|
|
49
|
+
print(f"[EXEPTION]: {msg}: {e}")
|
|
50
|
+
|
|
51
|
+
def __register_cmd(self, name: str, func: Callable, description: str = "", legacy=False):
|
|
52
|
+
name = name.lower()
|
|
53
|
+
if not description:
|
|
54
|
+
description = "A command"
|
|
55
|
+
if ' ' in name:
|
|
56
|
+
raise SyntaxError("Command name must not have spaces")
|
|
57
|
+
if name in self.commands:
|
|
58
|
+
raise SyntaxError(f"Command '{name}' is already registered. If theese commands have a different case and they need to stay the same, downgrade the package version to 0.5.x")
|
|
59
|
+
self.commands[name] = {"cmd": func, "description": description, "legacy": legacy, "is_async": inspect.iscoroutinefunction(func)}
|
|
60
|
+
|
|
61
|
+
def register_command(self, name: str, func: Callable, description: str = ""):
|
|
62
|
+
"""(DEPRECATED) Registers a command with its associated function."""
|
|
63
|
+
warnings.warn("Registering commands with `register_command` is deprecated, and should not be used.", DeprecationWarning, 2)
|
|
64
|
+
self.__register_cmd(name, func, description, legacy=True)
|
|
65
|
+
|
|
66
|
+
def command(self, *, name: str = "", description: str = ""):
|
|
67
|
+
"""Registers a command with its associated function as a decorator."""
|
|
68
|
+
def decorator(func: Callable):
|
|
69
|
+
lname = name or func.__name__
|
|
70
|
+
self.__register_cmd(lname, func, description)
|
|
71
|
+
return func
|
|
72
|
+
return decorator
|
|
73
|
+
|
|
74
|
+
async def start(self):
|
|
75
|
+
"""Starts the input handler loop in a separate thread if thread mode is enabled."""
|
|
76
|
+
self.is_running = True
|
|
77
|
+
|
|
78
|
+
while self.is_running:
|
|
79
|
+
try:
|
|
80
|
+
user_input = await asyncio.to_thread(input, self.cursor)
|
|
81
|
+
if not user_input:
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
cmdargs = user_input.split(' ')
|
|
85
|
+
command_name = cmdargs[0].lower()
|
|
86
|
+
args = cmdargs[1:]
|
|
87
|
+
if command_name in self.commands:
|
|
88
|
+
await _run_command(self.commands, command_name, args)
|
|
89
|
+
else:
|
|
90
|
+
self.__warning(f"Unknown command: '{command_name}'")
|
|
91
|
+
except EOFError:
|
|
92
|
+
self.__error("Input ended unexpectedly.")
|
|
93
|
+
break
|
|
94
|
+
except KeyboardInterrupt:
|
|
95
|
+
self.__error("Input interrupted.")
|
|
96
|
+
break
|
|
97
|
+
except HandlerClosed:
|
|
98
|
+
self.__info("Input Handler exited.")
|
|
99
|
+
break
|
|
100
|
+
self.is_running = False
|
|
101
|
+
|
|
102
|
+
async def _run_command(commands: dict, name: str, args: list):
|
|
103
|
+
"""Executes a command from the command dictionary if it exists."""
|
|
104
|
+
command = commands.get(name)
|
|
105
|
+
if not command:
|
|
106
|
+
self.__warning(f"Command '{name}' not found.")
|
|
107
|
+
|
|
108
|
+
func = command.get("cmd")
|
|
109
|
+
is_legacy = command.get("legacy", False)
|
|
110
|
+
is_async = command.get("is_async", False)
|
|
111
|
+
|
|
112
|
+
if not callable(func):
|
|
113
|
+
raise ValueError(f"The command '{name}' is not callable.")
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
sig = inspect.signature(func)
|
|
117
|
+
if is_legacy:
|
|
118
|
+
sig.bind(args)
|
|
119
|
+
else:
|
|
120
|
+
sig.bind(*args)
|
|
121
|
+
except TypeError as e:
|
|
122
|
+
self.__warning(f"Argument error for legacy command '{name}': {e}")
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
if is_legacy:
|
|
127
|
+
warnings.warn("This way of running commands id Deprecated. And should be changed to the new decorator way.", DeprecationWarning, 2)
|
|
128
|
+
await asyncio.to_thread(func, args)
|
|
129
|
+
elif is_async:
|
|
130
|
+
await func(*args)
|
|
131
|
+
else:
|
|
132
|
+
await asyncio.to_thread(func, *args)
|
|
133
|
+
|
|
134
|
+
except HandlerClosed as e:
|
|
135
|
+
raise e
|
|
136
|
+
except Exception as e:
|
|
137
|
+
self.__exeption(f"An error occurred in command '{name}'", e)
|
|
138
|
+
|
|
139
|
+
def register_default_commands(self):
|
|
140
|
+
@self.command(name="help", description="Displays all the available commands")
|
|
141
|
+
def help():
|
|
142
|
+
str_out = "Available commands:\n"
|
|
143
|
+
for command, data in self.commands.items():
|
|
144
|
+
str_out += f" {command}: {data['description']}\n"
|
|
145
|
+
print(str_out)
|
|
146
|
+
|
|
147
|
+
@self.command(name="debug", description="If a logger is present changes the logging level to DEBUG.")
|
|
148
|
+
def debug_mode():
|
|
149
|
+
logger = self.global_logger
|
|
150
|
+
if not logger:
|
|
151
|
+
return self.__warning("No logger defined for this InputHandler instance.")
|
|
152
|
+
|
|
153
|
+
if logger.getEffectiveLevel() == logging.DEBUG:
|
|
154
|
+
new_level = logging.INFO
|
|
155
|
+
message = "Debug mode is now off"
|
|
156
|
+
else:
|
|
157
|
+
new_level = logging.DEBUG
|
|
158
|
+
message = "Debug mode is now on"
|
|
159
|
+
|
|
160
|
+
logger.setLevel(new_level)
|
|
161
|
+
|
|
162
|
+
for handler in logger.handlers:
|
|
163
|
+
if isinstance(handler, logging.StreamHandler):
|
|
164
|
+
handler.setLevel(new_level)
|
|
165
|
+
self.__info(message)
|
|
166
|
+
|
|
167
|
+
@self.command(name="exit", description="Exits the Input Handler irreversibly.")
|
|
168
|
+
def exit_thread():
|
|
169
|
+
raise HandlerClosed("Handler was closed with exit command.")
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
from typing import Callable
|
|
2
|
+
from .exceptions import HandlerClosed
|
|
2
3
|
import logging, warnings
|
|
3
4
|
|
|
4
|
-
class HandlerClosed(Exception): ...
|
|
5
|
-
class MissingParameter(Exception): ...
|
|
6
|
-
|
|
7
5
|
class InputHandler:
|
|
8
6
|
def __init__(self, thread_mode = True, cursor = "", *, logger: logging.Logger | None = None, register_defaults: bool = True):
|
|
9
7
|
self.commands = {}
|
|
@@ -63,16 +61,14 @@ class InputHandler:
|
|
|
63
61
|
self.commands[name] = {"cmd": func, "description": description, "legacy": legacy}
|
|
64
62
|
|
|
65
63
|
def register_command(self, name: str, func: Callable, description: str = ""):
|
|
66
|
-
"""Registers a command with its associated function."""
|
|
67
|
-
warnings.warn("Registering commands with `register_command` is deprecated, and
|
|
64
|
+
"""(DEPRECATED) Registers a command with its associated function."""
|
|
65
|
+
warnings.warn("Registering commands with `register_command` is deprecated, and should not be used.", DeprecationWarning, 2)
|
|
68
66
|
self.__register_cmd(name, func, description, legacy=True)
|
|
69
67
|
|
|
70
68
|
def command(self, *, name: str = "", description: str = ""):
|
|
71
69
|
"""Registers a command with its associated function as a decorator."""
|
|
72
70
|
def decorator(func: Callable):
|
|
73
|
-
lname = name
|
|
74
|
-
if not lname:
|
|
75
|
-
lname = func.__name__
|
|
71
|
+
lname = name or func.__name__
|
|
76
72
|
self.__register_cmd(lname, func, description)
|
|
77
73
|
return func
|
|
78
74
|
return decorator
|
|
@@ -132,7 +128,7 @@ class InputHandler:
|
|
|
132
128
|
continue
|
|
133
129
|
|
|
134
130
|
cmdargs = user_input.split(' ')
|
|
135
|
-
command_name = cmdargs[0]
|
|
131
|
+
command_name = cmdargs[0].lower()
|
|
136
132
|
args = cmdargs[1:]
|
|
137
133
|
if command_name in self.commands:
|
|
138
134
|
_run_command(self.commands, command_name, args)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|