cli-ih 0.7.0__py3-none-any.whl → 0.7.1.1__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.
- cli_ih/asyncClient.py +49 -34
- cli_ih/client.py +50 -34
- cli_ih/utils.py +45 -8
- cli_ih-0.7.1.1.dist-info/METADATA +100 -0
- cli_ih-0.7.1.1.dist-info/RECORD +9 -0
- cli_ih-0.7.0.dist-info/METADATA +0 -86
- cli_ih-0.7.0.dist-info/RECORD +0 -9
- {cli_ih-0.7.0.dist-info → cli_ih-0.7.1.1.dist-info}/WHEEL +0 -0
- {cli_ih-0.7.0.dist-info → cli_ih-0.7.1.1.dist-info}/top_level.txt +0 -0
cli_ih/asyncClient.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Callable, Any
|
|
2
2
|
from .exceptions import HandlerClosed
|
|
3
|
-
from .utils import safe_print as print, register_handler
|
|
3
|
+
from .utils import safe_print as print, register_handler, SafeLogger
|
|
4
4
|
import logging, warnings, asyncio, inspect, threading, sys, shutil, msvcrt
|
|
5
5
|
|
|
6
6
|
class AsyncInputHandler:
|
|
@@ -10,12 +10,14 @@ class AsyncInputHandler:
|
|
|
10
10
|
self.is_running = False
|
|
11
11
|
self.thread_mode = thread_mode
|
|
12
12
|
self.cursor = f"{cursor.strip()} " if cursor else ""
|
|
13
|
-
self.global_logger = logger if logger else
|
|
14
|
-
self.logger = logger.getChild("InputHandler") if logger else
|
|
13
|
+
self.global_logger = logger if logger else SafeLogger()
|
|
14
|
+
self.logger = logger.getChild("InputHandler") if logger else self.global_logger
|
|
15
15
|
self.register_defaults = register_defaults
|
|
16
16
|
self.print_lock = threading.Lock()
|
|
17
17
|
self.input_buffer = ""
|
|
18
18
|
self.processing_command = False
|
|
19
|
+
self.history = []
|
|
20
|
+
self.history_index = 0
|
|
19
21
|
|
|
20
22
|
if self.register_defaults:
|
|
21
23
|
self.register_default_commands()
|
|
@@ -26,34 +28,19 @@ class AsyncInputHandler:
|
|
|
26
28
|
return self.logger
|
|
27
29
|
|
|
28
30
|
def __debug(self, msg: str):
|
|
29
|
-
|
|
30
|
-
self.logger.debug(msg)
|
|
31
|
-
else:
|
|
32
|
-
print(f"[DEBUG]: {msg}")
|
|
31
|
+
self.logger.debug(msg)
|
|
33
32
|
|
|
34
33
|
def __info(self, msg: str):
|
|
35
|
-
|
|
36
|
-
self.logger.info(msg)
|
|
37
|
-
else:
|
|
38
|
-
print(f"[INFO]: {msg}")
|
|
34
|
+
self.logger.info(msg)
|
|
39
35
|
|
|
40
36
|
def __warning(self, msg: str):
|
|
41
|
-
|
|
42
|
-
self.logger.warning(msg)
|
|
43
|
-
else:
|
|
44
|
-
print(f"[WARNING]: {msg}")
|
|
37
|
+
self.logger.warning(msg)
|
|
45
38
|
|
|
46
39
|
def __error(self, msg: str):
|
|
47
|
-
|
|
48
|
-
self.logger.error(msg)
|
|
49
|
-
else:
|
|
50
|
-
print(f"[ERROR]: {msg}")
|
|
40
|
+
self.logger.error(msg)
|
|
51
41
|
|
|
52
42
|
def __exeption(self, msg: str, e: Exception):
|
|
53
|
-
|
|
54
|
-
self.logger.exception(f"{msg}: {e}")
|
|
55
|
-
else:
|
|
56
|
-
print(f"[EXEPTION]: {msg}: {e}")
|
|
43
|
+
self.logger.exception(f"{msg}: {e}")
|
|
57
44
|
|
|
58
45
|
def __register_cmd(self, name: str, func: Callable[..., Any], description: str = "", legacy=False):
|
|
59
46
|
name = name.lower()
|
|
@@ -96,7 +83,6 @@ class AsyncInputHandler:
|
|
|
96
83
|
input_queue = asyncio.Queue()
|
|
97
84
|
|
|
98
85
|
def _input_worker():
|
|
99
|
-
# Initial prompt
|
|
100
86
|
with self.print_lock:
|
|
101
87
|
sys.stdout.write(self.cursor)
|
|
102
88
|
sys.stdout.flush()
|
|
@@ -106,37 +92,66 @@ class AsyncInputHandler:
|
|
|
106
92
|
if msvcrt.kbhit():
|
|
107
93
|
char = msvcrt.getwch()
|
|
108
94
|
|
|
109
|
-
if char == '\
|
|
95
|
+
if char == '\xe0' or char == '\x00':
|
|
96
|
+
try:
|
|
97
|
+
scancode = msvcrt.getwch()
|
|
98
|
+
if scancode == 'H':
|
|
99
|
+
if self.history_index > 0:
|
|
100
|
+
self.history_index -= 1
|
|
101
|
+
self.input_buffer = self.history[self.history_index]
|
|
102
|
+
with self.print_lock:
|
|
103
|
+
sys.stdout.write('\r' + ' ' * (shutil.get_terminal_size().columns - 1) + '\r')
|
|
104
|
+
sys.stdout.write(self.cursor + self.input_buffer)
|
|
105
|
+
sys.stdout.flush()
|
|
106
|
+
|
|
107
|
+
elif scancode == 'P':
|
|
108
|
+
if self.history_index < len(self.history):
|
|
109
|
+
self.history_index += 1
|
|
110
|
+
|
|
111
|
+
if self.history_index == len(self.history):
|
|
112
|
+
self.input_buffer = ""
|
|
113
|
+
else:
|
|
114
|
+
self.input_buffer = self.history[self.history_index]
|
|
115
|
+
|
|
116
|
+
with self.print_lock:
|
|
117
|
+
sys.stdout.write('\r' + ' ' * (shutil.get_terminal_size().columns - 1) + '\r')
|
|
118
|
+
sys.stdout.write(self.cursor + self.input_buffer)
|
|
119
|
+
sys.stdout.flush()
|
|
120
|
+
except Exception:
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
elif char == '\r':
|
|
110
124
|
with self.print_lock:
|
|
111
125
|
sys.stdout.write('\n')
|
|
112
126
|
sys.stdout.flush()
|
|
113
127
|
text = self.input_buffer
|
|
114
128
|
self.input_buffer = ""
|
|
129
|
+
|
|
130
|
+
if text:
|
|
131
|
+
if not self.history or self.history[-1] != text:
|
|
132
|
+
self.history.append(text)
|
|
133
|
+
self.history_index = len(self.history)
|
|
134
|
+
|
|
115
135
|
loop.call_soon_threadsafe(input_queue.put_nowait, text)
|
|
116
136
|
|
|
117
|
-
elif char == '\x08':
|
|
137
|
+
elif char == '\x08':
|
|
118
138
|
if len(self.input_buffer) > 0:
|
|
119
139
|
self.input_buffer = self.input_buffer[:-1]
|
|
120
140
|
with self.print_lock:
|
|
121
141
|
sys.stdout.write('\b \b')
|
|
122
142
|
sys.stdout.flush()
|
|
123
143
|
|
|
124
|
-
elif char == '\x03':
|
|
144
|
+
elif char == '\x03':
|
|
125
145
|
loop.call_soon_threadsafe(input_queue.put_nowait, KeyboardInterrupt)
|
|
126
146
|
break
|
|
127
147
|
|
|
128
148
|
else:
|
|
129
|
-
# Verify printable
|
|
130
149
|
if char.isprintable():
|
|
131
150
|
self.input_buffer += char
|
|
132
151
|
with self.print_lock:
|
|
133
152
|
sys.stdout.write(char)
|
|
134
153
|
sys.stdout.flush()
|
|
135
154
|
else:
|
|
136
|
-
pass
|
|
137
|
-
# Small sleep to prevent high CPU usage,
|
|
138
|
-
# but we are in a dedicated thread so it's fine-ish,
|
|
139
|
-
# actually explicit sleep is good.
|
|
140
155
|
import time
|
|
141
156
|
time.sleep(0.01)
|
|
142
157
|
|
|
@@ -146,7 +161,7 @@ class AsyncInputHandler:
|
|
|
146
161
|
thread = threading.Thread(target=_input_worker, daemon=True)
|
|
147
162
|
thread.start()
|
|
148
163
|
|
|
149
|
-
async def _run_command(commands: dict, name: str, args: list):
|
|
164
|
+
async def _run_command(commands: dict[str, dict[str, Any]], name: str, args: list[str]):
|
|
150
165
|
"""Executes a command from the command dictionary if it exists."""
|
|
151
166
|
command = commands.get(name)
|
|
152
167
|
if not command:
|
cli_ih/client.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from typing import Callable
|
|
1
|
+
from typing import Callable, Any
|
|
2
2
|
from .exceptions import HandlerClosed
|
|
3
|
-
from .utils import safe_print as print, register_handler
|
|
4
|
-
import logging, warnings,
|
|
3
|
+
from .utils import safe_print as print, register_handler, SafeLogger
|
|
4
|
+
import logging, sys, threading, warnings, inspect, shutil, msvcrt
|
|
5
5
|
|
|
6
6
|
class InputHandler:
|
|
7
7
|
def __init__(self, thread_mode = True, cursor = "", *, logger: logging.Logger | None = None, register_defaults: bool = True):
|
|
@@ -11,12 +11,14 @@ class InputHandler:
|
|
|
11
11
|
self.thread_mode = thread_mode
|
|
12
12
|
self.cursor = f"{cursor.strip()} " if cursor else ""
|
|
13
13
|
self.thread = None
|
|
14
|
-
self.global_logger = logger if logger else
|
|
15
|
-
self.logger = logger.getChild("InputHandler") if logger else
|
|
14
|
+
self.global_logger = logger if logger else SafeLogger()
|
|
15
|
+
self.logger = logger.getChild("InputHandler") if logger else self.global_logger
|
|
16
16
|
self.register_defaults = register_defaults
|
|
17
17
|
self.print_lock = threading.Lock()
|
|
18
18
|
self.input_buffer = ""
|
|
19
19
|
self.processing_command = False
|
|
20
|
+
self.history = []
|
|
21
|
+
self.history_index = 0
|
|
20
22
|
|
|
21
23
|
if self.register_defaults:
|
|
22
24
|
self.register_default_commands()
|
|
@@ -27,36 +29,21 @@ class InputHandler:
|
|
|
27
29
|
return self.logger
|
|
28
30
|
|
|
29
31
|
def __debug(self, msg: str):
|
|
30
|
-
|
|
31
|
-
self.logger.debug(msg)
|
|
32
|
-
else:
|
|
33
|
-
print(f"[DEBUG]: {msg}")
|
|
32
|
+
self.logger.debug(msg)
|
|
34
33
|
|
|
35
34
|
def __info(self, msg: str):
|
|
36
|
-
|
|
37
|
-
self.logger.info(msg)
|
|
38
|
-
else:
|
|
39
|
-
print(f"[INFO]: {msg}")
|
|
35
|
+
self.logger.info(msg)
|
|
40
36
|
|
|
41
37
|
def __warning(self, msg: str):
|
|
42
|
-
|
|
43
|
-
self.logger.warning(msg)
|
|
44
|
-
else:
|
|
45
|
-
print(f"[WARNING]: {msg}")
|
|
38
|
+
self.logger.warning(msg)
|
|
46
39
|
|
|
47
40
|
def __error(self, msg: str):
|
|
48
|
-
|
|
49
|
-
self.logger.error(msg)
|
|
50
|
-
else:
|
|
51
|
-
print(f"[ERROR]: {msg}")
|
|
41
|
+
self.logger.error(msg)
|
|
52
42
|
|
|
53
43
|
def __exeption(self, msg: str, e: Exception):
|
|
54
|
-
|
|
55
|
-
self.logger.exception(f"{msg}: {e}")
|
|
56
|
-
else:
|
|
57
|
-
print(f"[EXEPTION]: {msg}: {e}")
|
|
44
|
+
self.logger.exception(f"{msg}: {e}")
|
|
58
45
|
|
|
59
|
-
def __register_cmd(self, name: str, func: Callable, description: str = "", legacy=False):
|
|
46
|
+
def __register_cmd(self, name: str, func: Callable[..., Any], description: str = "", legacy=False):
|
|
60
47
|
name = name.lower()
|
|
61
48
|
if not description:
|
|
62
49
|
description = "A command"
|
|
@@ -66,14 +53,14 @@ class InputHandler:
|
|
|
66
53
|
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")
|
|
67
54
|
self.commands[name] = {"cmd": func, "description": description, "legacy": legacy}
|
|
68
55
|
|
|
69
|
-
def register_command(self, name: str, func: Callable, description: str = ""):
|
|
56
|
+
def register_command(self, name: str, func: Callable[..., Any], description: str = ""):
|
|
70
57
|
"""(DEPRECATED) Registers a command with its associated function."""
|
|
71
58
|
warnings.warn("Registering commands with `register_command` is deprecated, and should not be used.", DeprecationWarning, 2)
|
|
72
59
|
self.__register_cmd(name, func, description, legacy=True)
|
|
73
60
|
|
|
74
61
|
def command(self, *, name: str = "", description: str = ""):
|
|
75
62
|
"""Registers a command with its associated function as a decorator."""
|
|
76
|
-
def decorator(func: Callable):
|
|
63
|
+
def decorator(func: Callable[..., Any]):
|
|
77
64
|
lname = name or func.__name__
|
|
78
65
|
self.__register_cmd(lname, func, description)
|
|
79
66
|
return func
|
|
@@ -136,7 +123,6 @@ class InputHandler:
|
|
|
136
123
|
"""Continuously listens for user input and processes commands."""
|
|
137
124
|
while self.is_running:
|
|
138
125
|
try:
|
|
139
|
-
# Initial prompt
|
|
140
126
|
with self.print_lock:
|
|
141
127
|
sys.stdout.write(self.cursor)
|
|
142
128
|
sys.stdout.flush()
|
|
@@ -145,7 +131,35 @@ class InputHandler:
|
|
|
145
131
|
if msvcrt.kbhit():
|
|
146
132
|
char = msvcrt.getwch()
|
|
147
133
|
|
|
148
|
-
if char == '\
|
|
134
|
+
if char == '\xe0' or char == '\x00':
|
|
135
|
+
try:
|
|
136
|
+
scancode = msvcrt.getwch()
|
|
137
|
+
if scancode == 'H':
|
|
138
|
+
if self.history_index > 0:
|
|
139
|
+
self.history_index -= 1
|
|
140
|
+
self.input_buffer = self.history[self.history_index]
|
|
141
|
+
with self.print_lock:
|
|
142
|
+
sys.stdout.write('\r' + ' ' * (shutil.get_terminal_size().columns - 1) + '\r')
|
|
143
|
+
sys.stdout.write(self.cursor + self.input_buffer)
|
|
144
|
+
sys.stdout.flush()
|
|
145
|
+
|
|
146
|
+
elif scancode == 'P':
|
|
147
|
+
if self.history_index < len(self.history):
|
|
148
|
+
self.history_index += 1
|
|
149
|
+
|
|
150
|
+
if self.history_index == len(self.history):
|
|
151
|
+
self.input_buffer = ""
|
|
152
|
+
else:
|
|
153
|
+
self.input_buffer = self.history[self.history_index]
|
|
154
|
+
|
|
155
|
+
with self.print_lock:
|
|
156
|
+
sys.stdout.write('\r' + ' ' * (shutil.get_terminal_size().columns - 1) + '\r')
|
|
157
|
+
sys.stdout.write(self.cursor + self.input_buffer)
|
|
158
|
+
sys.stdout.flush()
|
|
159
|
+
except Exception:
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
elif char == '\r':
|
|
149
163
|
with self.print_lock:
|
|
150
164
|
sys.stdout.write('\n')
|
|
151
165
|
sys.stdout.flush()
|
|
@@ -153,6 +167,10 @@ class InputHandler:
|
|
|
153
167
|
self.input_buffer = ""
|
|
154
168
|
|
|
155
169
|
if text:
|
|
170
|
+
if not self.history or self.history[-1] != text:
|
|
171
|
+
self.history.append(text)
|
|
172
|
+
self.history_index = len(self.history)
|
|
173
|
+
|
|
156
174
|
self.processing_command = True
|
|
157
175
|
cmdargs = text.split(' ')
|
|
158
176
|
command_name = cmdargs[0].lower()
|
|
@@ -163,23 +181,21 @@ class InputHandler:
|
|
|
163
181
|
self.__warning(f"Unknown command: '{command_name}'")
|
|
164
182
|
self.processing_command = False
|
|
165
183
|
|
|
166
|
-
# Break inner loop to reprompt
|
|
167
184
|
break
|
|
168
185
|
|
|
169
|
-
elif char == '\x08':
|
|
186
|
+
elif char == '\x08':
|
|
170
187
|
if len(self.input_buffer) > 0:
|
|
171
188
|
self.input_buffer = self.input_buffer[:-1]
|
|
172
189
|
with self.print_lock:
|
|
173
190
|
sys.stdout.write('\b \b')
|
|
174
191
|
sys.stdout.flush()
|
|
175
192
|
|
|
176
|
-
elif char == '\x03':
|
|
193
|
+
elif char == '\x03':
|
|
177
194
|
self.__error("Input interrupted.")
|
|
178
195
|
self.is_running = False
|
|
179
196
|
return
|
|
180
197
|
|
|
181
198
|
else:
|
|
182
|
-
# Verify printable
|
|
183
199
|
if char.isprintable():
|
|
184
200
|
self.input_buffer += char
|
|
185
201
|
with self.print_lock:
|
cli_ih/utils.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import shutil
|
|
2
2
|
import sys
|
|
3
|
+
import threading
|
|
3
4
|
|
|
4
5
|
_HANDLER = None
|
|
5
6
|
|
|
@@ -7,15 +8,55 @@ def register_handler(handler):
|
|
|
7
8
|
global _HANDLER
|
|
8
9
|
_HANDLER = handler
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
class SafeLogger:
|
|
12
|
+
"""A dummy logger that uses safe_print to output logs to the console."""
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self.handlers = []
|
|
15
|
+
|
|
16
|
+
def debug(self, msg: str):
|
|
17
|
+
safe_print(f"[DEBUG]: {msg}")
|
|
18
|
+
|
|
19
|
+
def info(self, msg: str):
|
|
20
|
+
safe_print(f"[INFO]: {msg}")
|
|
21
|
+
|
|
22
|
+
def warning(self, msg: str):
|
|
23
|
+
safe_print(f"[WARNING]: {msg}")
|
|
24
|
+
|
|
25
|
+
def error(self, msg: str):
|
|
26
|
+
safe_print(f"[ERROR]: {msg}")
|
|
27
|
+
|
|
28
|
+
def critical(self, msg: str):
|
|
29
|
+
safe_print(f"[CRITICAL]: {msg}")
|
|
30
|
+
|
|
31
|
+
def exception(self, msg: str):
|
|
32
|
+
safe_print(f"[EXCEPTION]: {msg}")
|
|
33
|
+
|
|
34
|
+
def log(self, level, msg: str):
|
|
35
|
+
safe_print(f"[LOG {level}]: {msg}")
|
|
36
|
+
|
|
37
|
+
def getChild(self, name):
|
|
38
|
+
return self
|
|
39
|
+
|
|
40
|
+
def setLevel(self, level):
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
def getEffectiveLevel(self):
|
|
44
|
+
return 0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def safe_print(msg: object, cursor: str | None = None, input_buffer: str | None = None):
|
|
11
48
|
"""
|
|
12
49
|
Prints a message safely while preserving the current input buffer and cursor.
|
|
13
50
|
This ensures logs appear above the input line appropriately.
|
|
14
51
|
"""
|
|
52
|
+
try:
|
|
53
|
+
msg = str(msg)
|
|
54
|
+
except:
|
|
55
|
+
msg = "<Unprintable Object>"
|
|
56
|
+
|
|
15
57
|
if cursor is None and input_buffer is None and _HANDLER is not None:
|
|
16
|
-
lock = None
|
|
58
|
+
lock: threading.Lock | None = None
|
|
17
59
|
try:
|
|
18
|
-
# Try to acquire lock if available to prevent race conditions
|
|
19
60
|
lock = getattr(_HANDLER, "print_lock", None)
|
|
20
61
|
if lock:
|
|
21
62
|
lock.acquire()
|
|
@@ -33,8 +74,7 @@ def safe_print(msg: str, cursor: str | None = None, input_buffer: str | None = N
|
|
|
33
74
|
if lock:
|
|
34
75
|
lock.release()
|
|
35
76
|
else:
|
|
36
|
-
|
|
37
|
-
_do_safe_print(msg, cursor or "", input_buffer or "")
|
|
77
|
+
_do_safe_print(msg, str(cursor or ""), str(input_buffer or ""))
|
|
38
78
|
|
|
39
79
|
def _do_safe_print(msg: str, cursor: str, input_buffer: str):
|
|
40
80
|
try:
|
|
@@ -42,10 +82,7 @@ def _do_safe_print(msg: str, cursor: str, input_buffer: str):
|
|
|
42
82
|
except:
|
|
43
83
|
columns = 80
|
|
44
84
|
|
|
45
|
-
# Clear line
|
|
46
85
|
sys.stdout.write('\r' + ' ' * (columns - 1) + '\r')
|
|
47
|
-
# Print message
|
|
48
86
|
sys.stdout.write(f"{msg}\n")
|
|
49
|
-
# Reprint cursor and buffer
|
|
50
87
|
sys.stdout.write(f"{cursor}{input_buffer}")
|
|
51
88
|
sys.stdout.flush()
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cli_ih
|
|
3
|
+
Version: 0.7.1.1
|
|
4
|
+
Summary: A background command handler for python's command-line interface.
|
|
5
|
+
Author-email: Hotment <michatchuplay@gmail.com>
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# InputHandler Library
|
|
10
|
+
|
|
11
|
+
A lightweight Python library for creating interactive command-line interfaces with custom command registration, input handling, and clean log output. It supports synchronous and asynchronous modes, threaded input processing, and enhanced logging.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Command Registration**: Register commands with decorators and descriptions.
|
|
16
|
+
- **Threaded Input**: Non-blocking input handling by default.
|
|
17
|
+
- **Safe Printing**: Logs appear above the input line, preserving your typed text and cursor position.
|
|
18
|
+
- **Command History**: Navigate recent commands with Up/Down arrow keys.
|
|
19
|
+
- **Sync & Async**: Support for both synchronous and asynchronous (asyncio) applications.
|
|
20
|
+
- **Colored Logging**: Built-in support for colored log messages.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
`pip install cli_ih`
|
|
25
|
+
|
|
26
|
+
## Quick Start (Synchronous)
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from cli_ih import InputHandler, safe_print
|
|
30
|
+
|
|
31
|
+
handler = InputHandler(cursor="> ")
|
|
32
|
+
|
|
33
|
+
# Use safe_print instead of print to keep the input line clean!
|
|
34
|
+
@handler.command(name="greet", description="Greets the user.")
|
|
35
|
+
def greet(name):
|
|
36
|
+
safe_print(f"Hello, {name}!")
|
|
37
|
+
|
|
38
|
+
@handler.command(name="add", description="Adds two numbers.")
|
|
39
|
+
def add(a, b):
|
|
40
|
+
safe_print(int(a) + int(b))
|
|
41
|
+
|
|
42
|
+
handler.start()
|
|
43
|
+
|
|
44
|
+
# Using safe_print allows you to print logs in the background
|
|
45
|
+
# without messing up the user's current input line.
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Async Client Example
|
|
49
|
+
|
|
50
|
+
The `AsyncInputHandler` integrates with `asyncio`. The `start()` method is non-blocking when `thread_mode=True` (default).
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import asyncio
|
|
54
|
+
from cli_ih import AsyncInputHandler, safe_print
|
|
55
|
+
|
|
56
|
+
handler = AsyncInputHandler(cursor="Async> ")
|
|
57
|
+
|
|
58
|
+
@handler.command(name="greet", description="Greets the user asynchronously.")
|
|
59
|
+
async def greet(name):
|
|
60
|
+
await asyncio.sleep(1)
|
|
61
|
+
safe_print(f"Hello, {name}")
|
|
62
|
+
|
|
63
|
+
@handler.command(name="add", description="Adds two numbers.")
|
|
64
|
+
async def add(a, b):
|
|
65
|
+
safe_print(int(a) + int(b))
|
|
66
|
+
|
|
67
|
+
# Start the handler (runs in a separate thread by default)
|
|
68
|
+
handler.start()
|
|
69
|
+
|
|
70
|
+
# Keep the main thread alive or run your main event loop
|
|
71
|
+
async def main():
|
|
72
|
+
while handler.is_running:
|
|
73
|
+
await asyncio.sleep(1)
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
asyncio.run(main())
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Key Considerations
|
|
80
|
+
|
|
81
|
+
### Safe Printing
|
|
82
|
+
Always use `from cli_ih import safe_print` for outputting text to the console. This utility automatically detects the active input handler and ensures that your log message is printed *above* the current input line, preserving the user's cursor and any text they are currently typing.
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from cli_ih import safe_print
|
|
86
|
+
|
|
87
|
+
# Good
|
|
88
|
+
safe_print("Log message")
|
|
89
|
+
|
|
90
|
+
# Avoid (might disrupt input line)
|
|
91
|
+
print("Log message")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Thread Mode
|
|
95
|
+
Both `InputHandler` and `AsyncInputHandler` accept a `thread_mode` parameter (default `True`).
|
|
96
|
+
- `thread_mode=True`: The input loop runs in a separate thread. `start()` returns immediately.
|
|
97
|
+
- `thread_mode=False`: The input loop runs in the current thread. `start()` blocks until exit.
|
|
98
|
+
|
|
99
|
+
### Command History
|
|
100
|
+
Use the **Up** and **Down** arrow keys to cycle through your previously entered commands, just like in a standard terminal.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
cli_ih/__init__.py,sha256=wCwv9grczxpu7EXqunCdFgAWlx3XEZVw4O4U3fs45PY,278
|
|
2
|
+
cli_ih/asyncClient.py,sha256=56RLucih5qVNuxFR0EefExyohSheOh0BjomSoTMBEkU,12633
|
|
3
|
+
cli_ih/client.py,sha256=7jrFFrLdXln5VS4EG_YEse9oaa6n4FnUll5rvZNFiTc,12284
|
|
4
|
+
cli_ih/exceptions.py,sha256=0xqaMCVu-yEURIrB6wEhbf6kfdsRmODJBoDsQTOp29s,156
|
|
5
|
+
cli_ih/utils.py,sha256=3WcqvHrwwsiBYlkzIV51_SLsbtqpqEcN5v4Hu3E48Uw,2501
|
|
6
|
+
cli_ih-0.7.1.1.dist-info/METADATA,sha256=LO2NUmmDsuOAGtVEQsXuJZ7Cm3FuF45-1LyXPK7Nx7U,3413
|
|
7
|
+
cli_ih-0.7.1.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
8
|
+
cli_ih-0.7.1.1.dist-info/top_level.txt,sha256=Ve1CRLNXhPyPSkpN0xLu26roh30LQCpNzkF61BZYfk0,7
|
|
9
|
+
cli_ih-0.7.1.1.dist-info/RECORD,,
|
cli_ih-0.7.0.dist-info/METADATA
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: cli_ih
|
|
3
|
-
Version: 0.7.0
|
|
4
|
-
Summary: A background command handler for python's command-line interface.
|
|
5
|
-
Author-email: Hotment <michatchuplay@gmail.com>
|
|
6
|
-
Requires-Python: >=3.8
|
|
7
|
-
Description-Content-Type: text/markdown
|
|
8
|
-
|
|
9
|
-
# InputHandler Library
|
|
10
|
-
|
|
11
|
-
A lightweight Python library for creating interactive command-line interfaces with custom command registration and input handling. It supports threaded input processing and includes enhanced logging with color-coded output.
|
|
12
|
-
|
|
13
|
-
## Features
|
|
14
|
-
|
|
15
|
-
- Command registration system with descriptions
|
|
16
|
-
- Threaded or non-threaded input handling
|
|
17
|
-
- Colored logging with support for debug mode
|
|
18
|
-
- Built-in `help`, `debug`, and `exit` commands
|
|
19
|
-
- Error handling for missing or invalid command arguments
|
|
20
|
-
- NEW: Register commands with decorators
|
|
21
|
-
|
|
22
|
-
## Installation
|
|
23
|
-
|
|
24
|
-
`pip install cli_ih`
|
|
25
|
-
|
|
26
|
-
## Quick Start
|
|
27
|
-
|
|
28
|
-
```python
|
|
29
|
-
from cli_ih import InputHandler
|
|
30
|
-
|
|
31
|
-
def greet(args):
|
|
32
|
-
print(f"Hello, {' '.join(args)}!")
|
|
33
|
-
|
|
34
|
-
handler = InputHandler(cursor="> ")
|
|
35
|
-
# NEW
|
|
36
|
-
@handler.command(name="add", description="Performs the `+` operator on the first 2 arguments.") # The name param will use the func name if its not provided
|
|
37
|
-
def add(args):
|
|
38
|
-
print(int(args[0])+int(args[1]))
|
|
39
|
-
|
|
40
|
-
handler.register_command("greet", greet, "Greets the user. Usage: greet [name]")
|
|
41
|
-
handler.start()
|
|
42
|
-
|
|
43
|
-
# Now type commands like:
|
|
44
|
-
# > greet world
|
|
45
|
-
# Hello, world!
|
|
46
|
-
# > add 1 2
|
|
47
|
-
# 3
|
|
48
|
-
# > help
|
|
49
|
-
# Available commands:
|
|
50
|
-
# help: Displays all the available commands
|
|
51
|
-
# debug: If a logger is present changes the logging level to DEBUG.
|
|
52
|
-
# exit: Exits the Input Handler irreversibly.
|
|
53
|
-
# add: Performs the `+` operator on the first 2 arguments.
|
|
54
|
-
# greet: Greets the user. Usage: greet [name]
|
|
55
|
-
#
|
|
56
|
-
# > debug
|
|
57
|
-
# > exit
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## New Async client
|
|
61
|
-
```python
|
|
62
|
-
import asyncio
|
|
63
|
-
from cli_ih import AsyncInputHandler
|
|
64
|
-
|
|
65
|
-
print(cli_ih.__version__)
|
|
66
|
-
|
|
67
|
-
handler = AsyncInputHandler(cursor="> ")
|
|
68
|
-
|
|
69
|
-
@handler.command(name="greet", description="Greets the user. Usage: greet [name]")
|
|
70
|
-
async def greet(name, *args):
|
|
71
|
-
await asyncio.sleep(1)
|
|
72
|
-
print(f"Hello, {name}{" " if args else ""}{' '.join(args)}!")
|
|
73
|
-
# NEW
|
|
74
|
-
@handler.command(name="add", description="Performs the `+` operator on the first 2 arguments.")
|
|
75
|
-
async def add(a, b):
|
|
76
|
-
print(a+b)
|
|
77
|
-
|
|
78
|
-
asyncio.run(handler.start())
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## Additional Info
|
|
82
|
-
|
|
83
|
-
- You can provide a valid logger `logger=logger` to the `InputHandler` to enable logging (this will be removed soon)
|
|
84
|
-
- You can provide the `thread_mode` param to the `InputHandler` class to set if it shoud run in a thread or no.
|
|
85
|
-
(If you are using the `cli-ih` module on its own without any other background task set `thread_mode=False` to false)
|
|
86
|
-
- You can also provide a `cursor` param to the `InputHandler` class to set the cli cursor (default cusor is empty)
|
cli_ih-0.7.0.dist-info/RECORD
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
cli_ih/__init__.py,sha256=wCwv9grczxpu7EXqunCdFgAWlx3XEZVw4O4U3fs45PY,278
|
|
2
|
-
cli_ih/asyncClient.py,sha256=L7nCURM15Y9BIrSyfdeLB8PPddr1OF3IA8NWY26HyQQ,11171
|
|
3
|
-
cli_ih/client.py,sha256=GWZH5YX2dDV7A2usgnX9xDm8xVjwLSj09sh5vfHSW-Y,10573
|
|
4
|
-
cli_ih/exceptions.py,sha256=0xqaMCVu-yEURIrB6wEhbf6kfdsRmODJBoDsQTOp29s,156
|
|
5
|
-
cli_ih/utils.py,sha256=HstJPjjlTfVKz68sHU_whSBsOoBKhxizCcycNyQeK2k,1685
|
|
6
|
-
cli_ih-0.7.0.dist-info/METADATA,sha256=yu19a6SGAo79STCAgcxBVU4cqbvcYgIZ_KE7hyl_OcY,2789
|
|
7
|
-
cli_ih-0.7.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
8
|
-
cli_ih-0.7.0.dist-info/top_level.txt,sha256=Ve1CRLNXhPyPSkpN0xLu26roh30LQCpNzkF61BZYfk0,7
|
|
9
|
-
cli_ih-0.7.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|