This commit is contained in:
2025-11-21 19:41:35 +03:00
parent 8edd59c1b8
commit 2e76f68d4a
37 changed files with 395 additions and 482 deletions
+2 -4
View File
@@ -1,7 +1,5 @@
from argenta.app.autocompleter.entity import AutoCompleter as AutoCompleter
from argenta.app.defaults import PredefinedMessages as PredefinedMessages
from argenta.app.dividing_line.models import \
DynamicDividingLine as DynamicDividingLine
from argenta.app.dividing_line.models import \
StaticDividingLine as StaticDividingLine
from argenta.app.dividing_line.models import DynamicDividingLine as DynamicDividingLine
from argenta.app.dividing_line.models import StaticDividingLine as StaticDividingLine
from argenta.app.models import App as App
+8 -19
View File
@@ -6,10 +6,7 @@ from typing import Never
class AutoCompleter:
def __init__(
self, history_filename: str | None = None,
autocomplete_button: str = "tab"
) -> None:
def __init__(self, history_filename: str | None = None, autocomplete_button: str = "tab") -> None:
"""
Public. Configures and implements auto-completion of input command
:param history_filename: the name of the file for saving the history of the autocompleter
@@ -26,18 +23,12 @@ class AutoCompleter:
:param state: the current cursor position is relative to the beginning of the line
:return: the desired candidate as str or None
"""
matches: list[str] = sorted(
cmd for cmd in _get_history_items() if cmd.startswith(text)
)
matches: list[str] = sorted(cmd for cmd in _get_history_items() if cmd.startswith(text))
if len(matches) > 1:
common_prefix = matches[0]
for match in matches[1:]:
i = 0
while (
i < len(common_prefix)
and i < len(match)
and common_prefix[i] == match[i]
):
while i < len(common_prefix) and i < len(match) and common_prefix[i] == match[i]:
i += 1
common_prefix = common_prefix[:i]
if state == 0:
@@ -61,7 +52,7 @@ class AutoCompleter:
else:
for line in all_commands:
readline.add_history(line)
if not self.history_filename:
for line in all_commands:
readline.add_history(line)
@@ -85,19 +76,17 @@ class AutoCompleter:
pretty_history.append(line)
with open(self.history_filename, "w") as history_file:
_ = history_file.write("\n".join(pretty_history))
def _is_command_exist(command: str, existing_commands: list[str], ignore_command_register: bool) -> bool:
if ignore_command_register:
return command.lower() in existing_commands
return command in existing_commands
def _get_history_items() -> list[str] | list[Never]:
"""
Private. Returns a list of all commands entered by the user
:return: all commands entered by the user as list[str] | list[Never]
"""
return [
readline.get_history_item(i)
for i in range(1, readline.get_current_history_length() + 1)
]
return [readline.get_history_item(i) for i in range(1, readline.get_current_history_length() + 1)]
+1
View File
@@ -7,6 +7,7 @@ class PredefinedMessages(StrEnum):
"""
Public. A dataclass with predetermined messages for quick use
"""
USAGE = "[b dim]Usage[/b dim]: [i]<command> <[green]flags[/green]>[/i]"
HELP = "[b dim]Help[/b dim]: [i]<command>[/i] [b red]--help[/b red]"
AUTOCOMPLETE = "[b dim]Autocomplete[/b dim]: [i]<part>[/i] [bold]<tab>"
+2 -4
View File
@@ -1,4 +1,2 @@
from argenta.app.dividing_line.models import \
DynamicDividingLine as DynamicDividingLine
from argenta.app.dividing_line.models import \
StaticDividingLine as StaticDividingLine
from argenta.app.dividing_line.models import DynamicDividingLine as DynamicDividingLine
from argenta.app.dividing_line.models import StaticDividingLine as StaticDividingLine
+51 -93
View File
@@ -5,22 +5,25 @@ import re
from contextlib import redirect_stdout
from typing import Never, TypeAlias
from art import \
text2art # pyright: ignore[reportMissingTypeStubs, reportUnknownVariableType]
from art import text2art
from rich.console import Console
from rich.markup import escape
from argenta.app.autocompleter import AutoCompleter
from argenta.app.dividing_line.models import (DynamicDividingLine,
StaticDividingLine)
from argenta.app.protocols import (DescriptionMessageGenerator,
EmptyCommandHandler,
NonStandardBehaviorHandler, Printer)
from argenta.app.dividing_line.models import DynamicDividingLine, StaticDividingLine
from argenta.app.protocols import (
DescriptionMessageGenerator,
EmptyCommandHandler,
NonStandardBehaviorHandler,
Printer,
)
from argenta.app.registered_routers.entity import RegisteredRouters
from argenta.command.exceptions import (EmptyInputCommandException,
InputCommandException,
RepeatedInputFlagsException,
UnprocessedInputFlagException)
from argenta.command.exceptions import (
EmptyInputCommandException,
InputCommandException,
RepeatedInputFlagsException,
UnprocessedInputFlagException,
)
from argenta.command.models import Command, InputCommand
from argenta.response import Response
from argenta.router import Router
@@ -40,7 +43,7 @@ class BaseApp:
system_router_title: str | None,
ignore_command_register: bool,
dividing_line: StaticDividingLine | DynamicDividingLine,
repeat_command_groups: bool,
repeat_command_groups_printing: bool,
override_system_messages: bool,
autocompleter: AutoCompleter,
print_func: Printer,
@@ -51,7 +54,7 @@ class BaseApp:
self._system_router_title: str | None = system_router_title
self._dividing_line: StaticDividingLine | DynamicDividingLine = dividing_line
self._ignore_command_register: bool = ignore_command_register
self._repeat_command_groups_description: bool = repeat_command_groups
self._repeat_command_groups_printing_description: bool = repeat_command_groups_printing
self._override_system_messages: bool = override_system_messages
self._autocompleter: AutoCompleter = autocompleter
@@ -73,25 +76,21 @@ class BaseApp:
else self._matching_default_triggers_with_routers
)
self._incorrect_input_syntax_handler: NonStandardBehaviorHandler[str] = (
lambda _: print_func(f"Incorrect flag syntax: {_}")
self._incorrect_input_syntax_handler: NonStandardBehaviorHandler[str] = lambda _: print_func(
f"Incorrect flag syntax: {_}"
)
self._repeated_input_flags_handler: NonStandardBehaviorHandler[str] = (
lambda _: print_func(f"Repeated input flags: {_}")
self._repeated_input_flags_handler: NonStandardBehaviorHandler[str] = lambda _: print_func(
f"Repeated input flags: {_}"
)
self._empty_input_command_handler: EmptyCommandHandler = lambda: print_func(
"Empty input command"
self._empty_input_command_handler: EmptyCommandHandler = lambda: print_func("Empty input command")
self._unknown_command_handler: NonStandardBehaviorHandler[InputCommand] = lambda _: print_func(
f"Unknown command: {_.trigger}"
)
self._unknown_command_handler: NonStandardBehaviorHandler[InputCommand] = (
lambda _: print_func(f"Unknown command: {_.trigger}")
)
self._exit_command_handler: NonStandardBehaviorHandler[Response] = (
lambda _: print_func(self._farewell_message)
self._exit_command_handler: NonStandardBehaviorHandler[Response] = lambda _: print_func(
self._farewell_message
)
def set_description_message_pattern(
self, _: DescriptionMessageGenerator, /
) -> None:
def set_description_message_pattern(self, _: DescriptionMessageGenerator, /) -> None:
"""
Public. Sets the output pattern of the available commands
:param _: output pattern of the available commands
@@ -99,9 +98,7 @@ class BaseApp:
"""
self._description_message_gen = _
def set_incorrect_input_syntax_handler(
self, _: NonStandardBehaviorHandler[str], /
) -> None:
def set_incorrect_input_syntax_handler(self, _: NonStandardBehaviorHandler[str], /) -> None:
"""
Public. Sets the handler for incorrect flags when entering a command
:param _: handler for incorrect flags when entering a command
@@ -109,9 +106,7 @@ class BaseApp:
"""
self._incorrect_input_syntax_handler = _
def set_repeated_input_flags_handler(
self, _: NonStandardBehaviorHandler[str], /
) -> None:
def set_repeated_input_flags_handler(self, _: NonStandardBehaviorHandler[str], /) -> None:
"""
Public. Sets the handler for repeated flags when entering a command
:param _: handler for repeated flags when entering a command
@@ -119,9 +114,7 @@ class BaseApp:
"""
self._repeated_input_flags_handler = _
def set_unknown_command_handler(
self, _: NonStandardBehaviorHandler[InputCommand], /
) -> None:
def set_unknown_command_handler(self, _: NonStandardBehaviorHandler[InputCommand], /) -> None:
"""
Public. Sets the handler for unknown commands when entering a command
:param _: handler for unknown commands when entering a command
@@ -137,9 +130,7 @@ class BaseApp:
"""
self._empty_input_command_handler = _
def set_exit_command_handler(
self, _: NonStandardBehaviorHandler[Response], /
) -> None:
def set_exit_command_handler(self, _: NonStandardBehaviorHandler[Response], /) -> None:
"""
Public. Sets the handler for exit command when entering a command
:param _: handler for exit command when entering a command
@@ -175,11 +166,7 @@ class BaseApp:
clear_text = re.sub(r"\u001b\[[0-9;]*m", "", text)
max_length_line = max([len(line) for line in clear_text.split("\n")])
max_length_line = (
max_length_line
if 10 <= max_length_line <= 80
else 80
if max_length_line > 80
else 10
max_length_line if 10 <= max_length_line <= 80 else 80 if max_length_line > 80 else 10
)
self._print_func(
@@ -196,15 +183,11 @@ class BaseApp:
elif isinstance(self._dividing_line, StaticDividingLine): # pyright: ignore[reportUnnecessaryIsInstance]
self._print_func(
self._dividing_line.get_full_static_line(
is_override=self._override_system_messages
)
self._dividing_line.get_full_static_line(is_override=self._override_system_messages)
)
print(text.strip("\n"))
self._print_func(
self._dividing_line.get_full_static_line(
is_override=self._override_system_messages
)
self._dividing_line.get_full_static_line(is_override=self._override_system_messages)
)
else:
@@ -238,14 +221,10 @@ class BaseApp:
"""
input_command_trigger = command.trigger
if self._ignore_command_register:
if input_command_trigger.lower() in list(
self._current_matching_triggers_with_routers.keys()
):
if input_command_trigger.lower() in list(self._current_matching_triggers_with_routers.keys()):
return False
else:
if input_command_trigger in list(
self._current_matching_triggers_with_routers.keys()
):
if input_command_trigger in list(self._current_matching_triggers_with_routers.keys()):
return False
return True
@@ -303,9 +282,7 @@ class BaseApp:
:return: None
"""
self._prompt = f"[italic dim bold]{self._prompt}"
self._initial_message = (
"\n" + f"[bold red]{text2art(self._initial_message, font='tarty1')}" + "\n"
)
self._initial_message = "\n" + f"[bold red]{text2art(self._initial_message, font='tarty1')}" + "\n"
self._farewell_message = (
"[bold red]\n\n"
+ str(text2art(self._farewell_message, font="chanky")) # pyright: ignore[reportUnknownArgumentType]
@@ -323,20 +300,14 @@ class BaseApp:
self._repeated_input_flags_handler = lambda raw_command: self._print_func(
f"[red bold]Repeated input flags: {escape(raw_command)}"
)
self._empty_input_command_handler = lambda: self._print_func(
"[red bold]Empty input command"
)
self._empty_input_command_handler = lambda: self._print_func("[red bold]Empty input command")
def unknown_command_handler(command: InputCommand) -> None:
cmd_trg: str = command.trigger
mst_sim_cmd: str | None = self._most_similar_command(cmd_trg)
first_part_of_text = (
f"[red]Unknown command:[/red] [blue]{escape(cmd_trg)}[/blue]"
)
first_part_of_text = f"[red]Unknown command:[/red] [blue]{escape(cmd_trg)}[/blue]"
second_part_of_text = (
("[red], most similar:[/red] " + ("[blue]" + mst_sim_cmd + "[/blue]"))
if mst_sim_cmd
else ""
("[red], most similar:[/red] " + ("[blue]" + mst_sim_cmd + "[/blue]")) if mst_sim_cmd else ""
)
self._print_func(first_part_of_text + second_part_of_text)
@@ -356,13 +327,9 @@ class BaseApp:
for trigger in combined:
self._matching_default_triggers_with_routers[trigger] = router_entity
self._matching_lower_triggers_with_routers[trigger.lower()] = (
router_entity
)
self._matching_lower_triggers_with_routers[trigger.lower()] = router_entity
self._autocompleter.initial_setup(
list(self._current_matching_triggers_with_routers.keys())
)
self._autocompleter.initial_setup(list(self._current_matching_triggers_with_routers.keys()))
seen = {}
for item in list(self._current_matching_triggers_with_routers.keys()):
@@ -382,7 +349,7 @@ class BaseApp:
self._print_func(message)
if self._messages_on_startup:
print("\n")
if not self._repeat_command_groups_description:
if not self._repeat_command_groups_printing_description:
self._print_command_group_description()
@@ -405,7 +372,7 @@ class App(BaseApp):
system_router_title: str | None = "System points:",
ignore_command_register: bool = True,
dividing_line: AVAILABLE_DIVIDING_LINES = DEFAULT_DIVIDING_LINE,
repeat_command_groups: bool = True,
repeat_command_groups_printing: bool = True,
override_system_messages: bool = False,
autocompleter: AutoCompleter = DEFAULT_AUTOCOMPLETER,
print_func: Printer = DEFAULT_PRINT_FUNC,
@@ -420,7 +387,7 @@ class App(BaseApp):
:param system_router_title: system router title
:param ignore_command_register: whether to ignore the case of the entered commands
:param dividing_line: the entity of the dividing line
:param repeat_command_groups: whether to repeat the available commands and their description
:param repeat_command_groups_printing: whether to repeat the available commands and their description
:param override_system_messages: whether to redefine the default formatting of system messages
:param autocompleter: the entity of the autocompleter
:param print_func: system messages text output function
@@ -434,7 +401,7 @@ class App(BaseApp):
system_router_title=system_router_title,
ignore_command_register=ignore_command_register,
dividing_line=dividing_line,
repeat_command_groups=repeat_command_groups,
repeat_command_groups_printing=repeat_command_groups_printing,
override_system_messages=override_system_messages,
autocompleter=autocompleter,
print_func=print_func,
@@ -447,15 +414,13 @@ class App(BaseApp):
"""
self._pre_cycle_setup()
while True:
if self._repeat_command_groups_description:
if self._repeat_command_groups_printing_description:
self._print_command_group_description()
raw_command: str = Console().input(self._prompt)
try:
input_command: InputCommand = InputCommand.parse(
raw_command=raw_command
)
input_command: InputCommand = InputCommand.parse(raw_command=raw_command)
except InputCommandException as error:
with redirect_stdout(io.StringIO()) as stderr:
self._error_handler(error, raw_command)
@@ -466,8 +431,7 @@ class App(BaseApp):
if self._is_exit_command(input_command):
system_router.finds_appropriate_handler(input_command)
self._autocompleter.exit_setup(
list(self._current_matching_triggers_with_routers.keys()),
self._ignore_command_register
list(self._current_matching_triggers_with_routers.keys()), self._ignore_command_register
)
return
@@ -478,24 +442,18 @@ class App(BaseApp):
self._print_framed_text(stdout_res)
continue
processing_router = self._current_matching_triggers_with_routers[
input_command.trigger.lower()
]
processing_router = self._current_matching_triggers_with_routers[input_command.trigger.lower()]
if processing_router.disable_redirect_stdout:
dividing_line_unit_part: str = self._dividing_line.get_unit_part()
self._print_func(
StaticDividingLine(
dividing_line_unit_part
).get_full_static_line(
StaticDividingLine(dividing_line_unit_part).get_full_static_line(
is_override=self._override_system_messages
)
)
processing_router.finds_appropriate_handler(input_command)
self._print_func(
StaticDividingLine(
dividing_line_unit_part
).get_full_static_line(
StaticDividingLine(dividing_line_unit_part).get_full_static_line(
is_override=self._override_system_messages
)
)
+2 -1
View File
@@ -2,13 +2,14 @@ __all__ = ["NonStandardBehaviorHandler", "EmptyCommandHandler", "Printer", "Desc
from typing import Protocol, TypeVar
T = TypeVar('T', contravariant=True) # noqa: WPS111
T = TypeVar("T", contravariant=True) # noqa: WPS111
class NonStandardBehaviorHandler(Protocol[T]):
def __call__(self, __param: T) -> None:
raise NotImplementedError
class EmptyCommandHandler(Protocol):
def __call__(self) -> None:
raise NotImplementedError