From 83955aa0466e03f6ab4ee8ee71321bebf4ebbe4a Mon Sep 17 00:00:00 2001 From: kolo Date: Sun, 4 May 2025 02:13:05 +0300 Subject: [PATCH] first beta - adding hints for similar commands, now - feature freezing --- mock/local_test.py | 10 ++----- mock/mock_app/handlers/routers.py | 6 ++-- mock/mock_app/main.py | 2 +- pyproject.toml | 2 +- src/argenta/app/models.py | 49 +++++++++++++++++++++++-------- src/argenta/command/models.py | 10 +++---- src/argenta/router/entity.py | 15 +++++++--- 7 files changed, 58 insertions(+), 36 deletions(-) diff --git a/mock/local_test.py b/mock/local_test.py index c022e04..c47d85c 100644 --- a/mock/local_test.py +++ b/mock/local_test.py @@ -11,13 +11,7 @@ from argenta.router import Router from argenta.orchestrator import Orchestrator from argenta.command.models import InputCommand +from argenta.app.utils import most_similar_command -while True: - cmd = input(">>> ") - if cmd == "exit": - break - else: - parse_cmd: InputCommand = InputCommand.parse(cmd) - print(f'name: {parse_cmd.get_trigger()}\n' - f'flags: {parse_cmd.get_input_flags().get_flags()}\n') +print(most_similar_command('case', ['case', 'tester', 'poster', 'caser'])) diff --git a/mock/mock_app/handlers/routers.py b/mock/mock_app/handlers/routers.py index 33ac0ca..225a6b6 100644 --- a/mock/mock_app/handlers/routers.py +++ b/mock/mock_app/handlers/routers.py @@ -2,8 +2,7 @@ from rich.console import Console from argenta.command import Command from argenta.command.flag.defaults import PredefinedFlags -from argenta.command.flag.models import Flag -from argenta.command.flags.models import Flags +from argenta.command.flags import Flags from argenta.response import Response from argenta.router import Router @@ -13,8 +12,7 @@ work_router: Router = Router(title='Work points:') console = Console() -@work_router.command(Command('get', 'Get Help', aliases=['help', 'Get_help'], flags=Flags(PredefinedFlags.PORT, - PredefinedFlags.HOST))) +@work_router.command(Command('get', 'Get Help', aliases=['help', 'Get_help'], flags=Flags(PredefinedFlags.PORT, PredefinedFlags.HOST))) def command_help(response: Response): print(response.status) print(response.undefined_flags.get_flags()) diff --git a/mock/mock_app/main.py b/mock/mock_app/main.py index 0c8f112..5b8d988 100644 --- a/mock/mock_app/main.py +++ b/mock/mock_app/main.py @@ -12,7 +12,7 @@ from argenta.orchestrator.argparser.arguments import BooleanArgument arg_parser = ArgParser(processed_args=[BooleanArgument('repeat')]) app: App = App(dividing_line=DynamicDividingLine(), autocompleter=AutoCompleter('./mock/.hist'), - repeat_command_groups=False) + repeat_command_groups=False,) orchestrator: Orchestrator = Orchestrator(arg_parser) diff --git a/pyproject.toml b/pyproject.toml index 4ae02dd..5ae1ddd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "argenta" -version = "1.0.0-alpha5" +version = "1.0.0-beta1" description = "Python library for creating TUI" authors = [{ name = "kolo", email = "kolo.is.main@gmail.com" }] requires-python = ">=3.11, <4.0" diff --git a/src/argenta/app/models.py b/src/argenta/app/models.py index 2c5a15d..819c54f 100644 --- a/src/argenta/app/models.py +++ b/src/argenta/app/models.py @@ -219,22 +219,43 @@ class BaseApp: self._registered_routers.add_registered_router(system_router) + def _most_similar_command(self, unknown_command: str) -> str | None: + all_commands = self._all_registered_triggers_in_lower if self._ignore_command_register else self._all_registered_triggers_in_default_case + matches: list[str] | list = sorted(cmd for cmd in all_commands if cmd.startswith(unknown_command)) + if not matches: + matches: list[str] | list = sorted(cmd for cmd in all_commands if unknown_command.startswith(cmd)) + if len(matches) == 1: + return matches[0] + elif len(matches) > 1: + return sorted(matches, key=lambda cmd: len(cmd))[0] + else: + return None + + def _setup_default_view(self) -> None: """ Private. Sets up default app view :return: None """ - if not self._override_system_messages: - self._initial_message = f'\n[bold red]{text2art(self._initial_message, font="tarty1")}\n\n' - self._farewell_message = (f'[bold red]\n{text2art(f"\n{self._farewell_message}\n", font="chanky")}[/bold red]\n' - f'[red i]github.com/koloideal/Argenta[/red i] | [red bold i]made by kolo[/red bold i]\n') - self._description_message_gen = lambda command, description: (f'[bold red]{escape("[" + command + "]")}[/bold red] ' - f'[blue dim]*=*=*[/blue dim] ' - f'[bold yellow italic]{escape(description)}') - self._invalid_input_flags_handler = lambda raw_command: self._print_func(f'[red bold]Incorrect flag syntax: {escape(raw_command)}') - 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._unknown_command_handler = lambda command: self._print_func(f"[red bold]Unknown command: {escape(command.get_trigger())}") + self._prompt = '[italic dim bold]What do you want to do?\n' + self._initial_message = f'\n[bold red]{text2art(self._initial_message, font="tarty1")}\n\n' + self._farewell_message = (f'[bold red]\n{text2art(f"\n{self._farewell_message}\n", font="chanky")}[/bold red]\n' + f'[red i]github.com/koloideal/Argenta[/red i] | [red bold i]made by kolo[/red bold i]\n') + self._description_message_gen = lambda command, description: (f'[bold red]{escape("[" + command + "]")}[/bold red] ' + f'[blue dim]*=*=*[/blue dim] ' + f'[bold yellow italic]{escape(description)}') + self._invalid_input_flags_handler = lambda raw_command: self._print_func(f'[red bold]Incorrect flag syntax: {escape(raw_command)}') + 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') + + def unknown_command_handler(command: InputCommand) -> None: + cmd_trg: str = command.get_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]" + second_part_of_text = ('[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) + + self._unknown_command_handler = unknown_command_handler def _pre_cycle_setup(self) -> None: @@ -242,7 +263,6 @@ class BaseApp: Private. Configures various aspects of the application before the start of the cycle :return: None """ - self._setup_default_view() self._setup_system_router() for router_entity in self._registered_routers: @@ -254,6 +274,9 @@ class BaseApp: self._autocompleter.initial_setup(self._all_registered_triggers_in_lower) + if not self._override_system_messages: + self._setup_default_view() + self._print_func(self._initial_message) for message in self._messages_on_startup: @@ -268,7 +291,7 @@ class BaseApp: class App(BaseApp): def __init__(self, - prompt: str = '[italic dim bold]What do you want to do?\n', + prompt: str = 'What do you want to do?\n', initial_message: str = '\nArgenta\n', farewell_message: str = '\nSee you\n', exit_command: Command = Command('Q', 'Exit command'), diff --git a/src/argenta/command/models.py b/src/argenta/command/models.py index 7843ff3..8be7af9 100644 --- a/src/argenta/command/models.py +++ b/src/argenta/command/models.py @@ -139,11 +139,11 @@ class InputCommand(BaseCommand, Generic[InputCommandType]): for k, _ in enumerate(list_of_tokens): if _.startswith('-'): - if current_flag_name or len(_) < 2 or len(_[:_.rfind('-')]) > 3: + if len(_) < 2 or len(_[:_.rfind('-')]) > 3: raise UnprocessedInputFlagException() current_flag_name = _ else: - if not current_flag_name: + if not current_flag_name or current_flag_value: raise UnprocessedInputFlagException() current_flag_value = _ @@ -153,9 +153,9 @@ class InputCommand(BaseCommand, Generic[InputCommandType]): continue input_flag = InputFlag(name=current_flag_name[current_flag_name.rfind('-') + 1:], - prefix=cast(Literal['-', '--', '---'], - current_flag_name[:current_flag_name.rfind('-')+1]), - value=current_flag_value) + prefix=cast(Literal['-', '--', '---'], + current_flag_name[:current_flag_name.rfind('-')+1]), + value=current_flag_value) all_flags = [flag.get_string_entity() for flag in input_flags.get_flags()] if input_flag.get_string_entity() not in all_flags: diff --git a/src/argenta/router/entity.py b/src/argenta/router/entity.py index 8f46407..9905712 100644 --- a/src/argenta/router/entity.py +++ b/src/argenta/router/entity.py @@ -1,5 +1,7 @@ -from typing import Callable, Literal -from inspect import getfullargspec +from typing import Callable, Literal, Type +from inspect import getfullargspec, get_annotations +from rich.console import Console + from argenta.command import Command from argenta.command.models import InputCommand from argenta.response import Response, Status @@ -12,8 +14,7 @@ from argenta.router.exceptions import (RepeatedFlagNameException, class Router: - def __init__(self, - title: str = None): + def __init__(self, title: str = None): """ Public. Directly configures and manages handlers :param title: the title of the router, displayed when displaying the available commands @@ -157,6 +158,12 @@ class Router: elif len(transferred_args) == 0: raise RequiredArgumentNotPassedException() + arg_annotation: Type = get_annotations(func)[transferred_args[0]] + if not arg_annotation is Response: + Console().print(f'\n\n[b red]WARNING:[/b red] [i]The type of argument passed to the handler is [/i][blue]{Response}[/blue],' + f' [i]but[/i] [bold blue]{arg_annotation}[/bold blue] [i]is specified[/i]', highlight=False) + + def set_command_register_ignore(self, _: bool) -> None: """