diff --git a/mock/local_test.py b/mock/local_test.py index a4f7ca2..e69de29 100644 --- a/mock/local_test.py +++ b/mock/local_test.py @@ -1,14 +0,0 @@ -from rich.console import Console - -from argenta.app.presentation.renderers import RichRenderer, Renderer, PlainRenderer, RendererMixin - - -def main(rend: Renderer): - pass - -def mm(tr) -> str | None: - pass - -main(RichRenderer(print, mm)) -main(PlainRenderer(print, mm)) -main() \ No newline at end of file diff --git a/src/argenta/app/behavior_handlers/__init__.py b/src/argenta/app/behavior_handlers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/argenta/app/behavior_handlers/entity.py b/src/argenta/app/behavior_handlers/entity.py new file mode 100644 index 0000000..3e14fcf --- /dev/null +++ b/src/argenta/app/behavior_handlers/entity.py @@ -0,0 +1,62 @@ +from rich.markup import escape + +from argenta import Response +from argenta.app.presentation.renderers import Renderer +from argenta.app.protocols import ( + NonStandardBehaviorHandler, + EmptyCommandHandler, + Printer, + MostSimilarCommandGetter, + DescriptionMessageGenerator, +) +from argenta.command import InputCommand + + +class BehaviorHandlersFabric: + def __init__( + self, + printer: Printer, + renderer: Renderer, + most_similar_command_getter: MostSimilarCommandGetter, + ) -> None: + self._printer = printer + self._renderer = renderer + self._most_similar_command_getter = most_similar_command_getter + + def generate_incorrect_input_syntax_handler(self) -> NonStandardBehaviorHandler[str]: + return lambda raw_command: self._printer( + self._renderer.render_text_for_incorrect_input_syntax_handler( + raw_command=escape(raw_command) + ) + ) + + def generate_repeated_input_flags_handler(self) -> NonStandardBehaviorHandler[str]: + return lambda raw_command: self._printer( + self._renderer.render_text_for_repeated_input_flags_handler( + raw_command=escape(raw_command) + ) + ) + + def generate_empty_input_command_handler(self) -> EmptyCommandHandler: + return lambda: self._printer(self._renderer.render_text_for_empty_input_command_handler()) + + def generate_unknown_command_handler(self) -> NonStandardBehaviorHandler[InputCommand]: + def unknown_command_handler(command: InputCommand) -> None: + command_trigger: str = command.trigger + most_similar_command_trigger: str | None = self._most_similar_command_getter(command_trigger) + self._printer( + self._renderer.render_text_for_unknown_command_handler( + command_trigger=command_trigger, + most_similar_command_trigger=most_similar_command_trigger + ) + ) + return unknown_command_handler + + def generate_exit_command_handler(self, farewell_message: str) -> NonStandardBehaviorHandler[Response]: + return lambda _: self._printer(self._renderer.render_farewell_message(farewell_message)) + + def generate_description_message_generator(self) -> DescriptionMessageGenerator: + return lambda command, description: self._renderer.render_text_for_description_message_generator( + command=command, + description=description + ) diff --git a/src/argenta/app/models.py b/src/argenta/app/models.py index 5e9dcdd..17e6327 100644 --- a/src/argenta/app/models.py +++ b/src/argenta/app/models.py @@ -8,8 +8,10 @@ from typing import Callable, Never, TypeAlias from rich.console import Console from argenta.app.autocompleter import AutoCompleter +from argenta.app.behavior_handlers.entity import BehaviorHandlersFabric from argenta.app.presentation.renderers import PlainRenderer, RichRenderer, Renderer from argenta.app.dividing_line.models import DynamicDividingLine, StaticDividingLine +from argenta.app.presentation.viewers import Viewer from argenta.app.protocols import ( DescriptionMessageGenerator, EmptyCommandHandler, @@ -61,24 +63,25 @@ class BaseApp: self._messages_on_startup: list[str] = [] if self._override_system_messages: - self.view: Renderer = PlainRenderer( - print_func=self._print_func, - most_similar_command_getter=self._most_similar_command - ) + self._renderer: Renderer = PlainRenderer() else: - self.view: Renderer = RichRenderer( - print_func=self._print_func, - most_similar_command_getter=self._most_similar_command - ) + self._renderer: Renderer = RichRenderer() - self._initial_message: str = self.view.render_initial_message(initial_message) - self._farewell_message: str = self.view.render_farewell_message(farewell_message) - self._description_message_gen: DescriptionMessageGenerator = self.view.generate_formatted_description_message_gen() - self._incorrect_input_syntax_handler: NonStandardBehaviorHandler[str] = self.view.generate_formatted_incorrect_input_syntax_handler() - self._repeated_input_flags_handler: NonStandardBehaviorHandler[str] = self.view.generate_formatted_repeated_input_flags_handler() - self._empty_input_command_handler: EmptyCommandHandler = self.view.generate_formatted_empty_input_command_handler() - self._unknown_command_handler: NonStandardBehaviorHandler[InputCommand] = self.view.generate_formatted_unknown_command_handler() - self._exit_command_handler: NonStandardBehaviorHandler[Response] = self.view.generate_formatted_exit_command_handler(self._farewell_message) + self._viewer: Viewer = Viewer(self._print_func, self._renderer) + self._handlers_fabric: BehaviorHandlersFabric = BehaviorHandlersFabric( + self._print_func, + self._renderer, + self._most_similar_command + ) + + self._initial_message: str = self._renderer.render_initial_message(initial_message) + self._farewell_message: str = self._renderer.render_farewell_message(farewell_message) + self._description_message_generator: DescriptionMessageGenerator = self._handlers_fabric.generate_description_message_generator() + self._incorrect_input_syntax_handler: NonStandardBehaviorHandler[str] = self._handlers_fabric.generate_incorrect_input_syntax_handler() + self._repeated_input_flags_handler: NonStandardBehaviorHandler[str] = self._handlers_fabric.generate_repeated_input_flags_handler() + self._empty_input_command_handler: EmptyCommandHandler = self._handlers_fabric.generate_empty_input_command_handler() + self._unknown_command_handler: NonStandardBehaviorHandler[InputCommand] = self._handlers_fabric.generate_unknown_command_handler() + self._exit_command_handler: NonStandardBehaviorHandler[Response] = self._handlers_fabric.generate_exit_command_handler(self._farewell_message) def set_description_message_pattern(self, _: DescriptionMessageGenerator, /) -> None: """ @@ -86,7 +89,7 @@ class BaseApp: :param _: output pattern of the available commands :return: None """ - self._description_message_gen = _ + self._description_message_generator = _ def set_incorrect_input_syntax_handler(self, _: NonStandardBehaviorHandler[str], /) -> None: """ @@ -128,22 +131,6 @@ class BaseApp: """ self._exit_command_handler = _ - def _print_command_group_description(self) -> None: - """ - Private. Prints the description of the available commands - :return: None - """ - for registered_router in self.registered_routers: - self._print_func("\n" + registered_router.title) - for command_handler in registered_router.command_handlers: - handled_command = command_handler.handled_command - self._print_func( - self._description_message_gen( - handled_command.trigger, - handled_command.description, - ) - ) - def _print_static_framed_text(self, text: str) -> None: """ Private. Outputs text by framing it in a static or dynamic split strip @@ -278,10 +265,10 @@ class BaseApp: self._autocompleter.initial_setup(self.registered_routers.get_triggers()) if self._messages_on_startup: - self._print_func() + self._viewer.view_messages_on_startup(self._messages_on_startup) if not self._repeat_command_groups_printing: - self._print_command_group_description() + self._viewer.view_command_groups_description(self._description_message_generator, self.registered_routers) def _process_exist_and_valid_command(self, input_command: InputCommand) -> None: processing_router = self.registered_routers.get_router_by_trigger(input_command.trigger.lower()) @@ -290,6 +277,8 @@ class BaseApp: raise RuntimeError(f"Router for '{input_command.trigger}' not found. Panic!") match (self._dividing_line, processing_router.disable_redirect_stdout): + case (None, bool()): + processing_router.finds_appropriate_handler(input_command) case (DynamicDividingLine(), False): stdout_result = self._capture_stdout(lambda: processing_router.finds_appropriate_handler(input_command)) clear_text = _ANSI_ESCAPE_RE.sub("", stdout_result) @@ -321,8 +310,6 @@ class BaseApp: is_override=self._override_system_messages ) ) - case (None, bool()): - processing_router.finds_appropriate_handler(input_command) case _: raise NotImplementedError(f"Dividing line with type {self._dividing_line} is not implemented") @@ -384,10 +371,10 @@ class App(BaseApp): self._pre_cycle_setup() while True: if self._repeat_command_groups_printing: - self._print_command_group_description() + self._viewer.view_command_groups_description(self._description_message_generator, self.registered_routers) print() # pre-prompt gap - raw_command: str = self._autocompleter.prompt(self.view.render_prompt(self._prompt)) + raw_command: str = self._autocompleter.prompt(self._renderer.render_prompt(self._prompt)) print() # post-prompt gap try: diff --git a/src/argenta/app/presentation/renderers.py b/src/argenta/app/presentation/renderers.py index 30101ab..7e20d30 100644 --- a/src/argenta/app/presentation/renderers.py +++ b/src/argenta/app/presentation/renderers.py @@ -1,86 +1,56 @@ -from abc import ABC, abstractmethod -from typing import Protocol, Iterable +from typing import Iterable, Protocol from art import text2art -from rich.markup import escape +from argenta.app.protocols import DescriptionMessageGenerator from argenta.app.registered_routers.entity import RegisteredRouters -from argenta.response.entity import Response -from argenta.command.models import InputCommand -from argenta.app.protocols import ( - DescriptionMessageGenerator, - EmptyCommandHandler, - MostSimilarCommandGetter, - NonStandardBehaviorHandler, - Printer -) - - -class RendererMixin(ABC): - def __init__(self, print_func: Printer, most_similar_command_getter: MostSimilarCommandGetter) -> None: - self._print_func = print_func - self._most_similar_command_getter = most_similar_command_getter - - self._cached_command_groups_description: str | None = None - - @staticmethod - @abstractmethod - def generate_formatted_description_message_gen() -> DescriptionMessageGenerator: - raise NotImplementedError - - @staticmethod - def render_messages_on_startup(messages: Iterable[str]) -> str: - return "\n".join(messages) - - def render_command_groups_description(self, registered_routers: RegisteredRouters) -> str: - if self._cached_command_groups_description: - return self._cached_command_groups_description - command_groups_description = "" - for registered_router in registered_routers: - command_groups_description += "\n" + registered_router.title - for command_handler in registered_router.command_handlers: - handled_command = command_handler.handled_command - command_groups_description += self.generate_formatted_description_message_gen()( - handled_command.trigger, - handled_command.description, - ) - self._cached_command_groups_description = command_groups_description - return command_groups_description - - def generate_formatted_exit_command_handler(self, farewell_message: str) -> NonStandardBehaviorHandler[Response]: - return lambda _: self._print_func(farewell_message) class Renderer(Protocol): @staticmethod - def render_prompt(text: str) -> str: ... - + def render_prompt( + text: str + ) -> str: ... @staticmethod - def render_initial_message(text: str) -> str: ... - + def render_initial_message( + text: str + ) -> str: ... @staticmethod - def render_farewell_message(text: str) -> str: ... - + def render_farewell_message( + text: str + ) -> str: ... @staticmethod - def render_messages_on_startup(messages: Iterable[str]) -> str: ... - + def render_messages_on_startup( + messages: Iterable[str] + ) -> str: ... @staticmethod - def generate_formatted_description_message_gen() -> DescriptionMessageGenerator: ... - - def render_command_groups_description(self, registered_routers: RegisteredRouters) -> str: ... - - def generate_formatted_incorrect_input_syntax_handler(self) -> NonStandardBehaviorHandler[str]: ... - - def generate_formatted_repeated_input_flags_handler(self) -> NonStandardBehaviorHandler[str]: ... - - def generate_formatted_empty_input_command_handler(self) -> EmptyCommandHandler: ... - - def generate_formatted_unknown_command_handler(self) -> NonStandardBehaviorHandler[InputCommand]: ... - - def generate_formatted_exit_command_handler(self, farewell_message: str) -> NonStandardBehaviorHandler[Response]: ... + def render_text_for_description_message_generator( + command: str, + description: str + ) -> str: ... + @staticmethod + def render_command_groups_description( + description_message_generator: DescriptionMessageGenerator, + registered_routers: RegisteredRouters + ) -> str: ... + @staticmethod + def render_text_for_incorrect_input_syntax_handler( + raw_command: str + ) -> str: ... + @staticmethod + def render_text_for_repeated_input_flags_handler( + raw_command: str + ) -> str: ... + @staticmethod + def render_text_for_empty_input_command_handler() -> str: ... + @staticmethod + def render_text_for_unknown_command_handler( + command_trigger: str, + most_similar_command_trigger: str | None + ) -> str: ... -class RichRenderer(RendererMixin): +class RichRenderer(Renderer): @staticmethod def render_prompt(text: str) -> str: return f"{text}" @@ -99,35 +69,58 @@ class RichRenderer(RendererMixin): ) @staticmethod - def generate_formatted_description_message_gen() -> DescriptionMessageGenerator: - return lambda command, description: ( - f"[bold red]{escape('[' + command + ']')}[/bold red] " + def render_text_for_description_message_generator(command: str, description: str) -> str: + return ( + f"[bold red]<{command}>[/bold red] " f"[blue dim]*=*=*[/blue dim] " - f"[bold yellow italic]{escape(description)}[/bold yellow italic]" + f"[bold yellow italic]{description}[/bold yellow italic]" ) - def generate_formatted_incorrect_input_syntax_handler(self) -> NonStandardBehaviorHandler[str]: - return lambda raw_command: self._print_func(f"[red bold]Incorrect flag syntax: {escape(raw_command)}[/red bold]") + @staticmethod + def render_messages_on_startup(messages: Iterable[str]) -> str: + return "\n".join(messages) - def generate_formatted_repeated_input_flags_handler(self) -> NonStandardBehaviorHandler[str]: - return lambda raw_command: self._print_func(f"[red bold]Repeated input flags: {escape(raw_command)}[/red bold]") + @staticmethod + def render_command_groups_description( + description_message_generator: DescriptionMessageGenerator, + registered_routers: RegisteredRouters + ) -> str: + command_groups_description = "" + for registered_router in registered_routers: + command_groups_description += "\n" + registered_router.title + for command_handler in registered_router.command_handlers: + handled_command = command_handler.handled_command + command_groups_description += description_message_generator( + handled_command.trigger, + handled_command.description, + ) + return command_groups_description - def generate_formatted_empty_input_command_handler(self) -> EmptyCommandHandler: - return lambda: self._print_func("[red bold]Empty input command[/red bold]") + @staticmethod + def render_text_for_incorrect_input_syntax_handler(raw_command: str) -> str: + return f"[red bold]Incorrect flag syntax: {raw_command}[/red bold]" - def generate_formatted_unknown_command_handler(self) -> NonStandardBehaviorHandler[InputCommand]: - def unknown_command_handler(command: InputCommand) -> None: - cmd_trg: str = command.trigger - mst_sim_cmd: str | None = self._most_similar_command_getter(cmd_trg) - self._print_func( - f"[red]Unknown command:[/red][blue]{escape(cmd_trg)}[/blue][red]" - + (f", most similar:[/red][blue]{mst_sim_cmd}[/blue]" if mst_sim_cmd else "") - ) + @staticmethod + def render_text_for_repeated_input_flags_handler(raw_command: str) -> str: + return f"[red bold]Repeated input flags: {raw_command}[/red bold]" - return unknown_command_handler + @staticmethod + def render_text_for_empty_input_command_handler() -> str: + return "[red bold]Empty input command[/red bold]" + + @staticmethod + def render_text_for_unknown_command_handler( + command_trigger: str, + most_similar_command_trigger: str | None + ) -> str: + return ( + f"[red]Unknown command:[/red][blue]{command_trigger}[/blue]" + + (f"[red], most similar:[/red][blue]{most_similar_command_trigger}[/blue]" + if most_similar_command_trigger else "") + ) -class PlainRenderer(RendererMixin): +class PlainRenderer(Renderer): @staticmethod def render_prompt(text: str) -> str: return text @@ -138,28 +131,51 @@ class PlainRenderer(RendererMixin): @staticmethod def render_farewell_message(text: str) -> str: - return f"{text} | https://github.com/koloideal/Argenta | made by kolo" + return f"{text} | [https://github.com/koloideal/Argenta](https://github.com/koloideal/Argenta) | made by kolo" @staticmethod - def generate_formatted_description_message_gen() -> DescriptionMessageGenerator: - return lambda command, description: f"{command} *=*=* {description}" + def render_text_for_description_message_generator(command: str, description: str) -> str: + return f"{command} *=*=* {description}" - def generate_formatted_incorrect_input_syntax_handler(self) -> NonStandardBehaviorHandler[str]: - return lambda raw_command: self._print_func(f"Incorrect flag syntax: {escape(raw_command)}") + def render_messages_on_startup(self, messages: Iterable[str]) -> str: + return "\n".join(messages) - def generate_formatted_repeated_input_flags_handler(self) -> NonStandardBehaviorHandler[str]: - return lambda raw_command: self._print_func(f"Repeated input flags: {escape(raw_command)}") + @staticmethod + def render_command_groups_description( + description_message_generator: DescriptionMessageGenerator, + registered_routers: RegisteredRouters, + ) -> str: + command_groups_description = "" + for registered_router in registered_routers: + command_groups_description += "\n" + registered_router.title + for command_handler in registered_router.command_handlers: + handled_command = command_handler.handled_command + command_groups_description += description_message_generator( + handled_command.trigger, + handled_command.description, + ) + return command_groups_description - def generate_formatted_empty_input_command_handler(self) -> EmptyCommandHandler: - return lambda: self._print_func("Empty input command") + @staticmethod + def render_text_for_incorrect_input_syntax_handler(raw_command: str) -> str: + return f"Incorrect flag syntax: {raw_command}" - def generate_formatted_unknown_command_handler(self) -> NonStandardBehaviorHandler[InputCommand]: - def unknown_command_handler(command: InputCommand) -> None: - cmd_trg: str = command.trigger - mst_sim_cmd: str | None = self._most_similar_command_getter(cmd_trg) - self._print_func( - f"Unknown command: {escape(cmd_trg)}" - + (f", most similar:{mst_sim_cmd}" if mst_sim_cmd else "") - ) + @staticmethod + def render_text_for_repeated_input_flags_handler(raw_command: str) -> str: + return f"Repeated input flags: {raw_command}" + + @staticmethod + def render_text_for_empty_input_command_handler() -> str: + return "Empty input command" + + @staticmethod + def render_text_for_unknown_command_handler( + command_trigger: str, + most_similar_command_trigger: str | None + ) -> str: + return ( + f"Unknown command: {command_trigger}" + + (f", most similar:{most_similar_command_trigger}" + if most_similar_command_trigger else "") + ) - return unknown_command_handler diff --git a/src/argenta/app/presentation/viewers.py b/src/argenta/app/presentation/viewers.py index e69de29..decb547 100644 --- a/src/argenta/app/presentation/viewers.py +++ b/src/argenta/app/presentation/viewers.py @@ -0,0 +1,27 @@ +from typing import Iterable + +from argenta.app.presentation.renderers import Renderer +from argenta.app.protocols import Printer, DescriptionMessageGenerator +from argenta.app.registered_routers.entity import RegisteredRouters + + +class Viewer: + def __init__(self, printer: Printer, renderer: Renderer): + self._printer = printer + self._renderer = renderer + + def view_messages_on_startup(self, messages: Iterable[str]) -> None: + self._printer(self._renderer.render_messages_on_startup(messages)) + + def view_command_groups_description( + self, + description_message_generator: DescriptionMessageGenerator, + registered_routers: RegisteredRouters + ) -> None: + self._printer( + self._renderer.render_command_groups_description( + description_message_generator, + registered_routers + ) + ) + diff --git a/tests/unit_tests/test_app.py b/tests/unit_tests/test_app.py index d41b1a5..31e578d 100644 --- a/tests/unit_tests/test_app.py +++ b/tests/unit_tests/test_app.py @@ -343,7 +343,7 @@ def test_set_description_message_pattern_stores_generator() -> None: descr_gen: DescriptionMessageGenerator = lambda command, description: command + '-+-' + description app.set_description_message_pattern(descr_gen) - assert app._description_message_gen is descr_gen + assert app._description_message_generator is descr_gen def test_set_exit_command_handler_stores_handler() -> None: