mirror of
https://github.com/koloideal/Argenta.git
synced 2026-06-10 10:05:28 +03:00
docs
This commit is contained in:
@@ -0,0 +1,32 @@
|
|||||||
|
import sys
|
||||||
|
from unittest.mock import patch
|
||||||
|
import pytest
|
||||||
|
from pytest import CaptureFixture
|
||||||
|
|
||||||
|
from argenta import App, Orchestrator, Router, Command, Response
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def patched_argv():
|
||||||
|
with patch.object(sys, 'argv', ['program.py']):
|
||||||
|
yield
|
||||||
|
|
||||||
|
def test_input_incorrect_command(capsys: CaptureFixture[str]):
|
||||||
|
router = Router()
|
||||||
|
orchestrator = Orchestrator()
|
||||||
|
|
||||||
|
@router.command(Command('test'))
|
||||||
|
def test(response: Response) -> None:
|
||||||
|
print('test command')
|
||||||
|
|
||||||
|
app = App(override_system_messages=True, print_func=print)
|
||||||
|
app.include_router(router)
|
||||||
|
app.set_unknown_command_handler(
|
||||||
|
lambda command: print(f'Unknown command: {command.trigger}')
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch("builtins.input", side_effect=["help", "q"]):
|
||||||
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
|
output = capsys.readouterr().out
|
||||||
|
assert "\nUnknown command: help\n" in output
|
||||||
@@ -1,25 +1,21 @@
|
|||||||
import io
|
import io
|
||||||
import unittest
|
|
||||||
from contextlib import redirect_stdout
|
from contextlib import redirect_stdout
|
||||||
|
|
||||||
from argenta import App, Router, Command, Response
|
from argenta import App, Router, Command, Response
|
||||||
from argenta.command import InputCommand
|
from argenta.command import InputCommand
|
||||||
|
|
||||||
|
|
||||||
class TestAppIntegration(unittest.TestCase):
|
def test_simple_app() -> None:
|
||||||
def setUp(self) -> None:
|
app = App(override_system_messages=True, repeat_command_groups_printing=False)
|
||||||
self.app = App(override_system_messages=True, repeat_command_groups=False)
|
router = Router(title="App")
|
||||||
self.router = Router(title="App")
|
|
||||||
|
|
||||||
@self.router.command(Command("HELP", description="Show help"))
|
@router.command(Command("HELP", description="Show help"))
|
||||||
def help_cmd(response: Response):
|
def help_cmd(response: Response):
|
||||||
print("Available commands: HELP")
|
print("Available commands: HELP")
|
||||||
|
|
||||||
_ = help_cmd # appease linter: function is registered via decorator
|
app.include_router(router)
|
||||||
|
|
||||||
self.app.include_router(self.router)
|
with redirect_stdout(io.StringIO()) as stdout:
|
||||||
|
router.finds_appropriate_handler(InputCommand.parse("HELP"))
|
||||||
|
|
||||||
def test_help_command(self):
|
assert "Available commands:" in stdout.getvalue()
|
||||||
with redirect_stdout(io.StringIO()) as stdout:
|
|
||||||
self.router.finds_appropriate_handler(InputCommand.parse("HELP"))
|
|
||||||
self.assertIn("Available commands:", stdout.getvalue())
|
|
||||||
|
|||||||
@@ -1,59 +1,42 @@
|
|||||||
import io
|
import io
|
||||||
import unittest
|
|
||||||
from contextlib import redirect_stdout
|
from contextlib import redirect_stdout
|
||||||
from unittest.mock import Mock
|
|
||||||
|
|
||||||
from dishka import Provider, provide, make_container, Scope # pyright: ignore[reportUnknownVariableType]
|
from argenta.command import InputCommand
|
||||||
|
from dishka import Provider, make_container, Scope
|
||||||
|
|
||||||
from argenta import Router, Command
|
from argenta import Router, Response
|
||||||
from argenta.di.integration import inject, setup_dishka, FromDishka
|
from argenta.di.integration import setup_dishka, FromDishka
|
||||||
from argenta.response import Response
|
|
||||||
from argenta.response.status import ResponseStatus
|
|
||||||
|
|
||||||
|
|
||||||
class Service:
|
class Service:
|
||||||
def hello(self) -> str:
|
def hello(self) -> str:
|
||||||
return "world"
|
return "world"
|
||||||
|
|
||||||
|
def get_service() -> Service:
|
||||||
|
return Service()
|
||||||
|
|
||||||
|
|
||||||
router = Router(title="DI")
|
router = Router(title="DI")
|
||||||
|
|
||||||
|
@router.command("HELLO")
|
||||||
@router.command(Command("HELLO"))
|
|
||||||
@inject # Auto-inject dependencies from the Response container
|
|
||||||
def hello(response: Response, service: FromDishka[Service]) -> None:
|
def hello(response: Response, service: FromDishka[Service]) -> None:
|
||||||
print(f"hello {service.hello()}")
|
print(f"hello {service.hello()}")
|
||||||
|
|
||||||
|
|
||||||
class TestDIHandler(unittest.TestCase):
|
|
||||||
def test_hello_uses_service(self):
|
|
||||||
# Prepare DI container with a stub
|
|
||||||
fake = Mock(spec=Service)
|
|
||||||
fake.hello.return_value = "mocked"
|
|
||||||
|
|
||||||
class TestProvider(Provider):
|
|
||||||
scope = Scope.APP
|
|
||||||
|
|
||||||
@provide(scope=Scope.APP)
|
|
||||||
def service(self) -> Service:
|
|
||||||
return fake
|
|
||||||
|
|
||||||
container = make_container(TestProvider())
|
|
||||||
|
|
||||||
# Bind container to Response via integration
|
|
||||||
setup_dishka(app=_FakeApp(), container=container, auto_inject=False) # type: ignore[arg-type]
|
|
||||||
|
|
||||||
# Create Response bound to the container
|
|
||||||
r = Response(ResponseStatus.ALL_FLAGS_VALID)
|
|
||||||
r._dishka_container = container # direct assignment is acceptable in tests # pyright: ignore[reportPrivateUsage]
|
|
||||||
|
|
||||||
# Call handler directly
|
|
||||||
with redirect_stdout(io.StringIO()) as stdout:
|
|
||||||
hello(r)
|
|
||||||
|
|
||||||
self.assertIn("hello mocked", stdout.getvalue())
|
|
||||||
|
|
||||||
|
|
||||||
class _FakeApp:
|
class _FakeApp:
|
||||||
# Minimal stub for setup_dishka; app object is not used in unit tests
|
# Minimal stub for setup_dishka; app object is not used in unit tests
|
||||||
registered_routers = []
|
registered_routers = [router]
|
||||||
|
|
||||||
|
|
||||||
|
def test_hello_uses_service():
|
||||||
|
provider = Provider(scope=Scope.APP)
|
||||||
|
provider.provide(get_service)
|
||||||
|
|
||||||
|
container = make_container(provider)
|
||||||
|
setup_dishka(app=_FakeApp(), container=container, auto_inject=True)
|
||||||
|
|
||||||
|
# Call handler
|
||||||
|
with redirect_stdout(io.StringIO()) as stdout:
|
||||||
|
router.finds_appropriate_handler(InputCommand.parse('HELLO'))
|
||||||
|
|
||||||
|
assert "hello world" in stdout.getvalue()
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import io
|
import io
|
||||||
import unittest
|
|
||||||
from contextlib import redirect_stdout
|
from contextlib import redirect_stdout
|
||||||
|
|
||||||
from argenta import Router, Command, Response
|
from argenta import Router, Command, Response
|
||||||
@@ -8,15 +7,13 @@ from argenta.command import InputCommand
|
|||||||
|
|
||||||
router = Router(title="Demo")
|
router = Router(title="Demo")
|
||||||
|
|
||||||
|
|
||||||
@router.command(Command("PING", description="Ping command"))
|
@router.command(Command("PING", description="Ping command"))
|
||||||
def ping(response: Response):
|
def ping(response: Response):
|
||||||
print("PONG")
|
print("PONG")
|
||||||
|
|
||||||
|
|
||||||
class TestSimpleHandler(unittest.TestCase):
|
def test_ping_prints_pong():
|
||||||
def test_ping_prints_pong(self):
|
# Call handler
|
||||||
# Имитация запуска хендлера через роутер
|
with redirect_stdout(io.StringIO()) as stdout:
|
||||||
with redirect_stdout(io.StringIO()) as stdout:
|
router.finds_appropriate_handler(InputCommand.parse("PING"))
|
||||||
router.finds_appropriate_handler(InputCommand.parse("PING"))
|
assert "PONG" in stdout.getvalue()
|
||||||
self.assertIn("PONG", stdout.getvalue())
|
|
||||||
|
|||||||
+1
-5
@@ -12,7 +12,7 @@ default:
|
|||||||
@{{sphinxbuild}} -M help "{{sourcedir}}" "{{builddir}}" {{sphinxopts}}
|
@{{sphinxbuild}} -M help "{{sourcedir}}" "{{builddir}}" {{sphinxopts}}
|
||||||
|
|
||||||
# Build all language versions
|
# Build all language versions
|
||||||
build-all:
|
build:
|
||||||
{{sphinxbuild}} -b html -D language=ru {{sourcedir}} {{builddir}}/html/ru
|
{{sphinxbuild}} -b html -D language=ru {{sourcedir}} {{builddir}}/html/ru
|
||||||
{{sphinxbuild}} -b html -D language =en {{sourcedir}} {{builddir}}/html/en
|
{{sphinxbuild}} -b html -D language =en {{sourcedir}} {{builddir}}/html/en
|
||||||
|
|
||||||
@@ -28,7 +28,3 @@ live-en:
|
|||||||
update-langs:
|
update-langs:
|
||||||
{{sphinxbuild}} -b gettext . _build/gettext
|
{{sphinxbuild}} -b gettext . _build/gettext
|
||||||
sphinx-intl update -p _build/gettext -l en
|
sphinx-intl update -p _build/gettext -l en
|
||||||
|
|
||||||
# Generic build target (html, latex, etc.)
|
|
||||||
build target:
|
|
||||||
{{sphinxbuild}} -M {{target}} "{{sourcedir}}" "{{builddir}}" {{sphinxopts}}
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: ../../root/api/app/index.rst:47
|
#: ../../root/api/app/index.rst:47
|
||||||
msgid ""
|
msgid ""
|
||||||
"``repeat_command_groups``: Если ``True``, список доступных команд "
|
"``repeat_command_groups_printing``: Если ``True``, список доступных команд "
|
||||||
"выводится перед каждым вводом."
|
"выводится перед каждым вводом."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -296,7 +296,7 @@ msgstr ""
|
|||||||
#~ msgstr ""
|
#~ msgstr ""
|
||||||
|
|
||||||
#~ msgid ""
|
#~ msgid ""
|
||||||
#~ "``repeat_command_groups``: Если **True** (по "
|
#~ "``repeat_command_groups_printing``: Если **True** (по "
|
||||||
#~ "умолчанию), описание доступных команд будет"
|
#~ "умолчанию), описание доступных команд будет"
|
||||||
#~ " выводиться перед каждым вводом."
|
#~ " выводиться перед каждым вводом."
|
||||||
#~ msgstr ""
|
#~ msgstr ""
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ App
|
|||||||
system_router_title: str | None = "System points:",
|
system_router_title: str | None = "System points:",
|
||||||
ignore_command_register: bool = True,
|
ignore_command_register: bool = True,
|
||||||
dividing_line: AVAILABLE_DIVIDING_LINES = DEFAULT_DIVIDING_LINE,
|
dividing_line: AVAILABLE_DIVIDING_LINES = DEFAULT_DIVIDING_LINE,
|
||||||
repeat_command_groups: bool = True,
|
repeat_command_groups_printing: bool = True,
|
||||||
override_system_messages: bool = False,
|
override_system_messages: bool = False,
|
||||||
autocompleter: AutoCompleter = DEFAULT_AUTOCOMPLETER,
|
autocompleter: AutoCompleter = DEFAULT_AUTOCOMPLETER,
|
||||||
print_func: Printer = DEFAULT_PRINT_FUNC) -> None
|
print_func: Printer = DEFAULT_PRINT_FUNC) -> None
|
||||||
@@ -40,14 +40,14 @@ App
|
|||||||
* ``prompt``: Приглашение к вводу, отображаемое перед каждой командой.
|
* ``prompt``: Приглашение к вводу, отображаемое перед каждой командой.
|
||||||
* ``initial_message``: Сообщение, выводимое при запуске приложения.
|
* ``initial_message``: Сообщение, выводимое при запуске приложения.
|
||||||
* ``farewell_message``: Сообщение, выводимое при выходе из приложения.
|
* ``farewell_message``: Сообщение, выводимое при выходе из приложения.
|
||||||
* ``exit_command``: Команда, используемая для выхода из приложения.
|
* ``exit_command``: Команда, которая маркируется как триггер для выхода из приложения.
|
||||||
* ``system_router_title``: Заголовок для системного роутера (содержит команду выхода).
|
* ``system_router_title``: Заголовок для системного роутера (содержит команду выхода).
|
||||||
* ``ignore_command_register``: Если ``True``, регистр команд игнорируется при поиске обработчика.
|
* ``ignore_command_register``: Если ``True``, регистр вводимых команд игнорируется при поиске обработчика.
|
||||||
* ``dividing_line``: Стиль разделительной линии (``StaticDividingLine`` или ``DynamicDividingLine``).
|
* ``dividing_line``: Тип разделительной линии (``StaticDividingLine`` или ``DynamicDividingLine``).
|
||||||
* ``repeat_command_groups``: Если ``True``, список доступных команд выводится перед каждым вводом.
|
* ``repeat_command_groups_printing``: Если ``True``, список доступных команд выводится перед каждым вводом.
|
||||||
* ``override_system_messages``: Если ``True``, стандартное форматирование (цвета, ASCII-арт) отключается.
|
* ``override_system_messages``: Если ``True``, стандартное форматирование (цвета, ASCII-арт) отключается.
|
||||||
* ``autocompleter``: Объект, отвечающий за автодополнение команд.
|
* ``autocompleter``: Экземпляр класса :ref:`AutoCompleter <root_api_app_autocompleter>`, отвечающий за автодополнение команд.
|
||||||
* ``print_func``: Функция для вывода всех системных сообщений (по умолчанию ``rich.print``).
|
* ``print_func``: Функция для вывода всех системных сообщений (по умолчанию ``rich.Console().print``).
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ App
|
|||||||
|
|
||||||
- .. py:method:: add_message_on_startup(self, message: str) -> None
|
- .. py:method:: add_message_on_startup(self, message: str) -> None
|
||||||
|
|
||||||
Добавляет текстовое сообщение, которое выводится при запуске приложения после `initial_message`.
|
Добавляет текстовое сообщение, которое выводится при запуске приложения после ``initial_message``.
|
||||||
|
|
||||||
:param message: Строка с сообщением.
|
:param message: Строка с сообщением.
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ App
|
|||||||
|
|
||||||
.. py:method:: set_description_message_pattern(self, handler: Callable[[str, str], str]) -> None
|
.. py:method:: set_description_message_pattern(self, handler: Callable[[str, str], str]) -> None
|
||||||
|
|
||||||
Устанавливает шаблон для форматирования строки описания команды.
|
Устанавливает шаблон для форматирования описания команды.
|
||||||
|
|
||||||
Обработчик принимает триггер команды (``str``) и её описание (``str``), а возвращает отформатированную строку.
|
Обработчик принимает триггер команды (``str``) и её описание (``str``), а возвращает отформатированную строку.
|
||||||
|
|
||||||
|
|||||||
+12
-14
@@ -27,14 +27,12 @@
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from argenta import (
|
from argenta import App, Orchestrator, Router, Command, Response
|
||||||
App, Orchestrator, Router, Command, Response
|
|
||||||
)
|
|
||||||
|
|
||||||
* :ref:`App <root_api_app_index>` — Основной класс приложения.
|
* :ref:`App <root_api_app_index>` — Объект приложения, который отвечает за логику роутинга, настройки, валидации и т.д.
|
||||||
* :ref:`Orchestrator <root_api_orchestrator_index>` — Класс для управления жизненным циклом.
|
* :ref:`Orchestrator <root_api_orchestrator_index>` — Класс для конфигурирования и запуска всего приложения.
|
||||||
* :ref:`Router <root_api_router>` — Класс для группировки и регистрации команд.
|
* :ref:`Router <root_api_router>` — Класс для группировки и регистрации команд.
|
||||||
* :ref:`Command <root_api_command_index>` — Класс для создания команд.
|
* :ref:`Command <root_api_command_index>` — Класс для создания команд при инициализации хэндлеров.
|
||||||
* :ref:`Response <root_api_response>` — Объект ответа, передаваемый в обработчики.
|
* :ref:`Response <root_api_response>` — Объект ответа, передаваемый в обработчики.
|
||||||
|
|
||||||
.. rubric:: Команды и флаги
|
.. rubric:: Команды и флаги
|
||||||
@@ -55,9 +53,9 @@
|
|||||||
* :ref:`Flags <root_api_command_flags>` — Коллекция для регистрации флагов.
|
* :ref:`Flags <root_api_command_flags>` — Коллекция для регистрации флагов.
|
||||||
* :ref:`InputFlag <root_api_command_input_flag>` — Класс для введённого пользователем флага.
|
* :ref:`InputFlag <root_api_command_input_flag>` — Класс для введённого пользователем флага.
|
||||||
* :ref:`InputFlags <root_api_command_input_flags>` — Коллекция введённых флагов.
|
* :ref:`InputFlags <root_api_command_input_flags>` — Коллекция введённых флагов.
|
||||||
* :ref:`PossibleValues <root_api_command_possible_values>` — Правила валидации значений флагов.
|
* :ref:`PossibleValues <root_api_command_possible_values>` — Правила валидации значений флага.
|
||||||
* :ref:`ValidationStatus <root_api_command_validation_status>` — Статусы валидации флагов.
|
* :ref:`ValidationStatus <root_api_command_validation_status>` — Статусы валидации флагов.
|
||||||
* ``PredefinedFlags`` — Готовые наборы флагов (например, ``--help``).
|
* :ref:`PredefinedFlags <root_api_command_flag_predefined_flags>` — Коллекция предопределённых флагов.
|
||||||
|
|
||||||
.. rubric:: Настройка приложения
|
.. rubric:: Настройка приложения
|
||||||
|
|
||||||
@@ -70,10 +68,10 @@
|
|||||||
PredefinedMessages
|
PredefinedMessages
|
||||||
)
|
)
|
||||||
|
|
||||||
* :ref:`AutoCompleter <root_api_app_autocompleter>` — Базовый класс для автодополнения.
|
* :ref:`AutoCompleter <root_api_app_autocompleter>` - Класс для настройки автодополнения.
|
||||||
* :ref:`StaticDividingLine <root_api_app_dividing_lines>` — Статическая разделительная линия.
|
* :ref:`StaticDividingLine <root_api_app_dividing_lines>` — Статическая разделительная линия для оформления вывода.
|
||||||
* :ref:`DynamicDividingLine <root_api_app_dividing_lines>` — Динамическая разделительная линия.
|
* :ref:`DynamicDividingLine <root_api_app_dividing_lines>` — Динамическая разделительная линия для оформления вывода.
|
||||||
* ``PredefinedMessages`` — Готовые системные сообщения.
|
* :ref:`PredefinedMessages <root_api_predefined_messages>` — Готовые сообщения для вывода при старте приложения.
|
||||||
|
|
||||||
.. rubric:: Внедрение зависимостей
|
.. rubric:: Внедрение зависимостей
|
||||||
|
|
||||||
@@ -84,8 +82,8 @@
|
|||||||
inject
|
inject
|
||||||
)
|
)
|
||||||
|
|
||||||
* :ref:`FromDishka <root_dependency_injection>` — Маркер для внедрения зависимостей.
|
* :ref:`FromDishka <root_dependency_injection>` — Маркер аргумента функции как зависимости, которая должна быть инжектирована.
|
||||||
* :ref:`inject <root_dependency_injection>` — Декоратор для асинхронного внедрения.
|
* :ref:`inject <root_dependency_injection>` — Декоратор для инжектирования зависимостей, указанных в сигнатуре.
|
||||||
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
Наше обязательство
|
Наше обязательство
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
В целях создания открытой и гостеприимной атмосферы мы, как участники и мейнтейнеры, обязуемся сделать участие в нашем проекте и сообществе свободным от преследований для всех, независимо от возраста, телосложения, инвалидности, этнической принадлежности, гендерной идентичности и самовыражения, уровня опыта, образования, социально-экономического статуса, национальности, внешности, расы, религии или сексуальной идентичности и ориентации.
|
В целях создания открытой и гостеприимной атмосферы мы, как участники и мейнтейнеры, обязуемся сделать участие в нашем проекте и сообществе свободным от преследований для всех, независимо от возраста, телосложения, инвалидности, этнической принадлежности, уровня опыта, образования, социально-экономического статуса, национальности, внешности, расы или религии.
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|||||||
+16
-13
@@ -55,7 +55,7 @@
|
|||||||
|
|
||||||
Поищите ответ в существующих `Issues <https://github.com/koloideal/Argenta/issues>`_. Если вы нашли похожий вопрос, но всё ещё нуждаетесь в разъяснениях, можете написать в нём. Также рекомендуем поискать ответ в интернете.
|
Поищите ответ в существующих `Issues <https://github.com/koloideal/Argenta/issues>`_. Если вы нашли похожий вопрос, но всё ещё нуждаетесь в разъяснениях, можете написать в нём. Также рекомендуем поискать ответ в интернете.
|
||||||
|
|
||||||
Если ответа не нашлось, создайте новый `Issue <https://github.com/koloideal/Argenta/issues/new>`_ и предоставьте как можно больше контекста, включая версии проекта и платформы (CPython, pip и т.д.).
|
Если ответа не нашлось, создайте новый `Issue <https://github.com/koloideal/Argenta/issues/new>`_ и предоставьте как можно больше контекста, включая версии проекта и платформы.
|
||||||
|
|
||||||
Мы займемся вашей задачей как можно скорее.
|
Мы займемся вашей задачей как можно скорее.
|
||||||
|
|
||||||
@@ -87,10 +87,10 @@
|
|||||||
* Также поищите в интернете (включая `Stack Overflow`), чтобы узнать, обсуждалась ли проблема за пределами `GitHub`.
|
* Также поищите в интернете (включая `Stack Overflow`), чтобы узнать, обсуждалась ли проблема за пределами `GitHub`.
|
||||||
* Соберите информацию об ошибке:
|
* Соберите информацию об ошибке:
|
||||||
* Трассировка стека.
|
* Трассировка стека.
|
||||||
* ОС, платформа и версия (Windows, Linux, macOS, x86, ARM).
|
* ОС, платформа и версия (Windows, Linux, macOS, x86, ARM).
|
||||||
* Версия интерпретатора, компилятора, SDK, среды выполнения, менеджера пакетов и т.д.
|
* Версия интерпретатора, компилятора, SDK, среды выполнения, менеджера пакетов и т.д.
|
||||||
* Входные данные и полученный результат.
|
* Входные данные и полученный результат.
|
||||||
* Можете ли вы надёжно воспроизвести проблему? Воспроизводится ли она на старых версиях?
|
* Можете ли вы надёжно воспроизвести проблему? Воспроизводится ли она на старых версиях?
|
||||||
|
|
||||||
.. rubric:: Как мне отправить хороший отчет об ошибке?
|
.. rubric:: Как мне отправить хороший отчет об ошибке?
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@
|
|||||||
|
|
||||||
python -m pytest tests
|
python -m pytest tests
|
||||||
|
|
||||||
#. Сделайте коммит, следуя нашему руководству по стилю, и отправьте изменения в ваш форк.
|
#. Сделайте коммит, следуя :ref:`нашему руководству по стилю <styleguide>`, и отправьте изменения в ваш форк.
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
@@ -210,6 +210,10 @@
|
|||||||
|
|
||||||
Мы поддерживаем документацию на двух языках: русском и английском.
|
Мы поддерживаем документацию на двух языках: русском и английском.
|
||||||
|
|
||||||
|
.. important::
|
||||||
|
|
||||||
|
Для инкапсуляции различных команд, необходимых для настройки и запуска проекта мы используем ``just``, он же фигурирует в различных примерах в документации, поэтому рекомендуем вам `установить его <https://github.com/casey/just#installation>`_
|
||||||
|
|
||||||
Для улучшения документации вы можете следовать процессу, похожему на внесение вклада в код:
|
Для улучшения документации вы можете следовать процессу, похожему на внесение вклада в код:
|
||||||
|
|
||||||
#. Убедитесь, что ваше окружение для разработки настроено, как описано в разделе `Ваш первый вклад в код`_.
|
#. Убедитесь, что ваше окружение для разработки настроено, как описано в разделе `Ваш первый вклад в код`_.
|
||||||
@@ -220,33 +224,32 @@
|
|||||||
cd docs
|
cd docs
|
||||||
|
|
||||||
#. Внесите изменения в **русскую** версию документации (`docs/index.rst` и/или `docs/root/*`).
|
#. Внесите изменения в **русскую** версию документации (`docs/index.rst` и/или `docs/root/*`).
|
||||||
#. Чтобы собрать документацию локально и увидеть изменения, выполните:
|
#. Чтобы собрать документацию локально в режиме автоматического ребилда и увидеть изменения, выполните:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
make live-ru
|
just live-ru
|
||||||
|
|
||||||
#. Откройте `127.0.0.1:8000` в браузере, чтобы просмотреть сгенерированную документацию.
|
#. Откройте `127.0.0.1:8000` в браузере, чтобы просмотреть сгенерированную документацию.
|
||||||
#. После завершения работы над русской версией необходимо создать английский перевод:
|
#. После завершения работы над русской версией необходимо создать английский перевод:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
make update-langs
|
just update-langs
|
||||||
|
|
||||||
#. После обновления шаблона обновите файлы перевода, расположенные в `docs/locales/en/LC_MESSAGES/`.
|
#. После обновления шаблона обновите файлы перевода, расположенные в `docs/locales/en/LC_MESSAGES/`.
|
||||||
#. Когда изменения будут готовы, сделайте коммит и откройте `Pull Request`. Используйте префикс `docs:` в сообщении коммита.
|
#. Когда изменения будут готовы, сделайте коммит и откройте `Pull Request`. Используйте префикс `docs:` в сообщении коммита.
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
|
||||||
.. _Руководства по стилю:
|
.. _styleguide:
|
||||||
|
|
||||||
Руководства по стилю
|
Руководства по стилю
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
.. _Сообщения коммитов:
|
.. _commits_messages:
|
||||||
|
|
||||||
Сообщения коммитов
|
**Сообщения коммитов**
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Мы следуем спецификации `Conventional Commits <https://www.conventionalcommits.org/en/v1.0.0/>`_. Это делает историю проекта более читаемой и позволяет автоматически генерировать журнал изменений.
|
Мы следуем спецификации `Conventional Commits <https://www.conventionalcommits.org/en/v1.0.0/>`_. Это делает историю проекта более читаемой и позволяет автоматически генерировать журнал изменений.
|
||||||
|
|
||||||
|
|||||||
+19
-4
@@ -1,19 +1,21 @@
|
|||||||
Тестирование
|
Тестирование
|
||||||
============
|
============
|
||||||
|
|
||||||
В этом разделе описаны практики тестирования приложений на основе ``Argenta``. Примеры основаны на фактическом публичном API: ``App``, ``Router``, ``Command``, ``Orchestrator``, DI через ``dishka`` и интеграцию в ``argenta.di.integration``.
|
В этом разделе описаны практики тестирования приложений на основе ``Argenta``. Примеры основаны на фактическом публичном API.
|
||||||
|
|
||||||
Модульное тестирование хендлеров
|
Модульное тестирование хендлеров
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
Обработчики в Argenta — обычные функции. Их удобно тестировать как чистые функции, не поднимая весь цикл приложения. Рекомендуются ``unittest`` или ``pytest``.
|
Обработчики в Argenta — обычные функции. Их удобно тестировать как чистые функции, не поднимая весь цикл приложения. Рекомендуются ``unittest`` или ``pytest``.
|
||||||
|
|
||||||
Пример с ``unittest`` для простого хендлера без DI:
|
Пример для простого хендлера без DI:
|
||||||
|
|
||||||
.. literalinclude:: ../code_snippets/testing/simple_handler_unittest.py
|
.. literalinclude:: ../code_snippets/testing/simple_handler_unittest.py
|
||||||
:language: python
|
:language: python
|
||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
Тестирование с внедрением зависимостей (DI)
|
Тестирование с внедрением зависимостей (DI)
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
|
|
||||||
@@ -23,6 +25,8 @@
|
|||||||
:language: python
|
:language: python
|
||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
Интеграционное тестирование приложения
|
Интеграционное тестирование приложения
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
|
|
||||||
@@ -32,10 +36,21 @@
|
|||||||
:language: python
|
:language: python
|
||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
E2E-тестирование цикла (опционально)
|
-----
|
||||||
|
|
||||||
|
E2E-тестирование цикла
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
Полный запуск цикла ``start_polling`` можно покрывать через подпроцесс с передачей строк во ``stdin``. Это тяжелее и обычно не требуется. Если всё же необходимо — вынесите конфигурацию в функцию ``main()`` и запускайте модуль в подпроцессе с подготовленным вводом/выводом.
|
Полный запуск цикла ``start_polling`` можно покрывать через подпроцесс с передачей строк в ``stdin``. Это тяжелее и обычно не требуется. Если всё же необходимо — пример ниже.
|
||||||
|
|
||||||
|
.. danger ::
|
||||||
|
Обязательно передавайте строковый триггер команды выхода последним элементом в списке, который передаёте в контекстном менеджере при патче ``input`` как аргумент ``side_effects``, иначе тестируемое приложение будет ожидать ввода следующей команды и не сможет корректно завершиться.
|
||||||
|
|
||||||
|
.. literalinclude:: ../code_snippets/testing/app_e2e_test.py
|
||||||
|
:language: python
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
Советы по тестированию
|
Советы по тестированию
|
||||||
----------------------
|
----------------------
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
set windows-shell := ["powershell.exe", "-NoLogo", "-Command"]
|
||||||
|
set shell := ["bash", "-c"]
|
||||||
|
|
||||||
|
# Вывести список всех рецептов
|
||||||
|
default:
|
||||||
|
@just --list
|
||||||
|
|
||||||
|
# Запустить тесты через pytest
|
||||||
|
tests:
|
||||||
|
python -m pytest tests
|
||||||
|
|
||||||
|
# Запустить тесты с отчетом о покрытии
|
||||||
|
tests-cov:
|
||||||
|
python -m pytest --cov=argenta tests
|
||||||
|
|
||||||
|
# Отформатировать код (Ruff + isort)
|
||||||
|
format:
|
||||||
|
python -m ruff format ./src
|
||||||
|
python -m isort ./src
|
||||||
|
|
||||||
|
# Проверить типы через mypy (strict)
|
||||||
|
mypy:
|
||||||
|
python -m mypy -p argenta --strict
|
||||||
|
|
||||||
|
# Проверить стиль через wemake-python-styleguide
|
||||||
|
wps:
|
||||||
|
python -m flake8 --format=wemake ./src
|
||||||
|
|
||||||
|
# Запустить линтер Ruff
|
||||||
|
ruff:
|
||||||
|
python -m ruff check ./src
|
||||||
|
|
||||||
@@ -45,6 +45,7 @@ exclude = [
|
|||||||
".__pycache__",
|
".__pycache__",
|
||||||
"tests"
|
"tests"
|
||||||
]
|
]
|
||||||
|
line-length=110
|
||||||
|
|
||||||
[tool.pyright]
|
[tool.pyright]
|
||||||
typeCheckingMode = "strict"
|
typeCheckingMode = "strict"
|
||||||
@@ -52,6 +53,9 @@ typeCheckingMode = "strict"
|
|||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
disable_error_code = "import-untyped"
|
disable_error_code = "import-untyped"
|
||||||
|
|
||||||
|
[tool.isort]
|
||||||
|
line_length=90
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["hatchling"]
|
requires = ["hatchling"]
|
||||||
build-backend = "hatchling.build"
|
build-backend = "hatchling.build"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from argenta.app.models import App as App
|
from argenta.app.models import App as App
|
||||||
from argenta.command.models import Command as Command
|
from argenta.command.models import Command as Command
|
||||||
|
from argenta.data_bridge.entity import DataBridge as DataBridge
|
||||||
from argenta.orchestrator.entity import Orchestrator as Orchestrator
|
from argenta.orchestrator.entity import Orchestrator as Orchestrator
|
||||||
from argenta.response.entity import Response as Response
|
from argenta.response.entity import Response as Response
|
||||||
from argenta.router.entity import Router as Router
|
from argenta.router.entity import Router as Router
|
||||||
from argenta.data_bridge.entity import DataBridge as DataBridge
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
from argenta.app.autocompleter.entity import AutoCompleter as AutoCompleter
|
from argenta.app.autocompleter.entity import AutoCompleter as AutoCompleter
|
||||||
from argenta.app.defaults import PredefinedMessages as PredefinedMessages
|
from argenta.app.defaults import PredefinedMessages as PredefinedMessages
|
||||||
from argenta.app.dividing_line.models import \
|
from argenta.app.dividing_line.models import DynamicDividingLine as DynamicDividingLine
|
||||||
DynamicDividingLine as DynamicDividingLine
|
from argenta.app.dividing_line.models import StaticDividingLine as StaticDividingLine
|
||||||
from argenta.app.dividing_line.models import \
|
|
||||||
StaticDividingLine as StaticDividingLine
|
|
||||||
from argenta.app.models import App as App
|
from argenta.app.models import App as App
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ from typing import Never
|
|||||||
|
|
||||||
|
|
||||||
class AutoCompleter:
|
class AutoCompleter:
|
||||||
def __init__(
|
def __init__(self, history_filename: str | None = None, autocomplete_button: str = "tab") -> None:
|
||||||
self, history_filename: str | None = None,
|
|
||||||
autocomplete_button: str = "tab"
|
|
||||||
) -> None:
|
|
||||||
"""
|
"""
|
||||||
Public. Configures and implements auto-completion of input command
|
Public. Configures and implements auto-completion of input command
|
||||||
:param history_filename: the name of the file for saving the history of the autocompleter
|
: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
|
:param state: the current cursor position is relative to the beginning of the line
|
||||||
:return: the desired candidate as str or None
|
:return: the desired candidate as str or None
|
||||||
"""
|
"""
|
||||||
matches: list[str] = sorted(
|
matches: list[str] = sorted(cmd for cmd in _get_history_items() if cmd.startswith(text))
|
||||||
cmd for cmd in _get_history_items() if cmd.startswith(text)
|
|
||||||
)
|
|
||||||
if len(matches) > 1:
|
if len(matches) > 1:
|
||||||
common_prefix = matches[0]
|
common_prefix = matches[0]
|
||||||
for match in matches[1:]:
|
for match in matches[1:]:
|
||||||
i = 0
|
i = 0
|
||||||
while (
|
while i < len(common_prefix) and i < len(match) and common_prefix[i] == match[i]:
|
||||||
i < len(common_prefix)
|
|
||||||
and i < len(match)
|
|
||||||
and common_prefix[i] == match[i]
|
|
||||||
):
|
|
||||||
i += 1
|
i += 1
|
||||||
common_prefix = common_prefix[:i]
|
common_prefix = common_prefix[:i]
|
||||||
if state == 0:
|
if state == 0:
|
||||||
@@ -92,12 +83,10 @@ def _is_command_exist(command: str, existing_commands: list[str], ignore_command
|
|||||||
return command.lower() in existing_commands
|
return command.lower() in existing_commands
|
||||||
return command in existing_commands
|
return command in existing_commands
|
||||||
|
|
||||||
|
|
||||||
def _get_history_items() -> list[str] | list[Never]:
|
def _get_history_items() -> list[str] | list[Never]:
|
||||||
"""
|
"""
|
||||||
Private. Returns a list of all commands entered by the user
|
Private. Returns a list of all commands entered by the user
|
||||||
:return: all commands entered by the user as list[str] | list[Never]
|
:return: all commands entered by the user as list[str] | list[Never]
|
||||||
"""
|
"""
|
||||||
return [
|
return [readline.get_history_item(i) for i in range(1, readline.get_current_history_length() + 1)]
|
||||||
readline.get_history_item(i)
|
|
||||||
for i in range(1, readline.get_current_history_length() + 1)
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ class PredefinedMessages(StrEnum):
|
|||||||
"""
|
"""
|
||||||
Public. A dataclass with predetermined messages for quick use
|
Public. A dataclass with predetermined messages for quick use
|
||||||
"""
|
"""
|
||||||
|
|
||||||
USAGE = "[b dim]Usage[/b dim]: [i]<command> <[green]flags[/green]>[/i]"
|
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]"
|
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>"
|
AUTOCOMPLETE = "[b dim]Autocomplete[/b dim]: [i]<part>[/i] [bold]<tab>"
|
||||||
|
|||||||
@@ -1,4 +1,2 @@
|
|||||||
from argenta.app.dividing_line.models import \
|
from argenta.app.dividing_line.models import DynamicDividingLine as DynamicDividingLine
|
||||||
DynamicDividingLine as DynamicDividingLine
|
from argenta.app.dividing_line.models import StaticDividingLine as StaticDividingLine
|
||||||
from argenta.app.dividing_line.models import \
|
|
||||||
StaticDividingLine as StaticDividingLine
|
|
||||||
|
|||||||
+51
-93
@@ -5,22 +5,25 @@ import re
|
|||||||
from contextlib import redirect_stdout
|
from contextlib import redirect_stdout
|
||||||
from typing import Never, TypeAlias
|
from typing import Never, TypeAlias
|
||||||
|
|
||||||
from art import \
|
from art import text2art
|
||||||
text2art # pyright: ignore[reportMissingTypeStubs, reportUnknownVariableType]
|
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.markup import escape
|
from rich.markup import escape
|
||||||
|
|
||||||
from argenta.app.autocompleter import AutoCompleter
|
from argenta.app.autocompleter import AutoCompleter
|
||||||
from argenta.app.dividing_line.models import (DynamicDividingLine,
|
from argenta.app.dividing_line.models import DynamicDividingLine, StaticDividingLine
|
||||||
StaticDividingLine)
|
from argenta.app.protocols import (
|
||||||
from argenta.app.protocols import (DescriptionMessageGenerator,
|
DescriptionMessageGenerator,
|
||||||
EmptyCommandHandler,
|
EmptyCommandHandler,
|
||||||
NonStandardBehaviorHandler, Printer)
|
NonStandardBehaviorHandler,
|
||||||
|
Printer,
|
||||||
|
)
|
||||||
from argenta.app.registered_routers.entity import RegisteredRouters
|
from argenta.app.registered_routers.entity import RegisteredRouters
|
||||||
from argenta.command.exceptions import (EmptyInputCommandException,
|
from argenta.command.exceptions import (
|
||||||
InputCommandException,
|
EmptyInputCommandException,
|
||||||
RepeatedInputFlagsException,
|
InputCommandException,
|
||||||
UnprocessedInputFlagException)
|
RepeatedInputFlagsException,
|
||||||
|
UnprocessedInputFlagException,
|
||||||
|
)
|
||||||
from argenta.command.models import Command, InputCommand
|
from argenta.command.models import Command, InputCommand
|
||||||
from argenta.response import Response
|
from argenta.response import Response
|
||||||
from argenta.router import Router
|
from argenta.router import Router
|
||||||
@@ -40,7 +43,7 @@ class BaseApp:
|
|||||||
system_router_title: str | None,
|
system_router_title: str | None,
|
||||||
ignore_command_register: bool,
|
ignore_command_register: bool,
|
||||||
dividing_line: StaticDividingLine | DynamicDividingLine,
|
dividing_line: StaticDividingLine | DynamicDividingLine,
|
||||||
repeat_command_groups: bool,
|
repeat_command_groups_printing: bool,
|
||||||
override_system_messages: bool,
|
override_system_messages: bool,
|
||||||
autocompleter: AutoCompleter,
|
autocompleter: AutoCompleter,
|
||||||
print_func: Printer,
|
print_func: Printer,
|
||||||
@@ -51,7 +54,7 @@ class BaseApp:
|
|||||||
self._system_router_title: str | None = system_router_title
|
self._system_router_title: str | None = system_router_title
|
||||||
self._dividing_line: StaticDividingLine | DynamicDividingLine = dividing_line
|
self._dividing_line: StaticDividingLine | DynamicDividingLine = dividing_line
|
||||||
self._ignore_command_register: bool = ignore_command_register
|
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._override_system_messages: bool = override_system_messages
|
||||||
self._autocompleter: AutoCompleter = autocompleter
|
self._autocompleter: AutoCompleter = autocompleter
|
||||||
|
|
||||||
@@ -73,25 +76,21 @@ class BaseApp:
|
|||||||
else self._matching_default_triggers_with_routers
|
else self._matching_default_triggers_with_routers
|
||||||
)
|
)
|
||||||
|
|
||||||
self._incorrect_input_syntax_handler: NonStandardBehaviorHandler[str] = (
|
self._incorrect_input_syntax_handler: NonStandardBehaviorHandler[str] = lambda _: print_func(
|
||||||
lambda _: print_func(f"Incorrect flag syntax: {_}")
|
f"Incorrect flag syntax: {_}"
|
||||||
)
|
)
|
||||||
self._repeated_input_flags_handler: NonStandardBehaviorHandler[str] = (
|
self._repeated_input_flags_handler: NonStandardBehaviorHandler[str] = lambda _: print_func(
|
||||||
lambda _: print_func(f"Repeated input flags: {_}")
|
f"Repeated input flags: {_}"
|
||||||
)
|
)
|
||||||
self._empty_input_command_handler: EmptyCommandHandler = lambda: print_func(
|
self._empty_input_command_handler: EmptyCommandHandler = lambda: print_func("Empty input command")
|
||||||
"Empty input command"
|
self._unknown_command_handler: NonStandardBehaviorHandler[InputCommand] = lambda _: print_func(
|
||||||
|
f"Unknown command: {_.trigger}"
|
||||||
)
|
)
|
||||||
self._unknown_command_handler: NonStandardBehaviorHandler[InputCommand] = (
|
self._exit_command_handler: NonStandardBehaviorHandler[Response] = lambda _: print_func(
|
||||||
lambda _: print_func(f"Unknown command: {_.trigger}")
|
self._farewell_message
|
||||||
)
|
|
||||||
self._exit_command_handler: NonStandardBehaviorHandler[Response] = (
|
|
||||||
lambda _: print_func(self._farewell_message)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_description_message_pattern(
|
def set_description_message_pattern(self, _: DescriptionMessageGenerator, /) -> None:
|
||||||
self, _: DescriptionMessageGenerator, /
|
|
||||||
) -> None:
|
|
||||||
"""
|
"""
|
||||||
Public. Sets the output pattern of the available commands
|
Public. Sets the output pattern of the available commands
|
||||||
:param _: output pattern of the available commands
|
:param _: output pattern of the available commands
|
||||||
@@ -99,9 +98,7 @@ class BaseApp:
|
|||||||
"""
|
"""
|
||||||
self._description_message_gen = _
|
self._description_message_gen = _
|
||||||
|
|
||||||
def set_incorrect_input_syntax_handler(
|
def set_incorrect_input_syntax_handler(self, _: NonStandardBehaviorHandler[str], /) -> None:
|
||||||
self, _: NonStandardBehaviorHandler[str], /
|
|
||||||
) -> None:
|
|
||||||
"""
|
"""
|
||||||
Public. Sets the handler for incorrect flags when entering a command
|
Public. Sets the handler for incorrect flags when entering a command
|
||||||
:param _: 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 = _
|
self._incorrect_input_syntax_handler = _
|
||||||
|
|
||||||
def set_repeated_input_flags_handler(
|
def set_repeated_input_flags_handler(self, _: NonStandardBehaviorHandler[str], /) -> None:
|
||||||
self, _: NonStandardBehaviorHandler[str], /
|
|
||||||
) -> None:
|
|
||||||
"""
|
"""
|
||||||
Public. Sets the handler for repeated flags when entering a command
|
Public. Sets the handler for repeated flags when entering a command
|
||||||
:param _: 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 = _
|
self._repeated_input_flags_handler = _
|
||||||
|
|
||||||
def set_unknown_command_handler(
|
def set_unknown_command_handler(self, _: NonStandardBehaviorHandler[InputCommand], /) -> None:
|
||||||
self, _: NonStandardBehaviorHandler[InputCommand], /
|
|
||||||
) -> None:
|
|
||||||
"""
|
"""
|
||||||
Public. Sets the handler for unknown commands when entering a command
|
Public. Sets the handler for unknown commands when entering a command
|
||||||
:param _: 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 = _
|
self._empty_input_command_handler = _
|
||||||
|
|
||||||
def set_exit_command_handler(
|
def set_exit_command_handler(self, _: NonStandardBehaviorHandler[Response], /) -> None:
|
||||||
self, _: NonStandardBehaviorHandler[Response], /
|
|
||||||
) -> None:
|
|
||||||
"""
|
"""
|
||||||
Public. Sets the handler for exit command when entering a command
|
Public. Sets the handler for exit command when entering a command
|
||||||
:param _: 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)
|
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([len(line) for line in clear_text.split("\n")])
|
||||||
max_length_line = (
|
max_length_line = (
|
||||||
max_length_line
|
max_length_line if 10 <= max_length_line <= 80 else 80 if max_length_line > 80 else 10
|
||||||
if 10 <= max_length_line <= 80
|
|
||||||
else 80
|
|
||||||
if max_length_line > 80
|
|
||||||
else 10
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self._print_func(
|
self._print_func(
|
||||||
@@ -196,15 +183,11 @@ class BaseApp:
|
|||||||
|
|
||||||
elif isinstance(self._dividing_line, StaticDividingLine): # pyright: ignore[reportUnnecessaryIsInstance]
|
elif isinstance(self._dividing_line, StaticDividingLine): # pyright: ignore[reportUnnecessaryIsInstance]
|
||||||
self._print_func(
|
self._print_func(
|
||||||
self._dividing_line.get_full_static_line(
|
self._dividing_line.get_full_static_line(is_override=self._override_system_messages)
|
||||||
is_override=self._override_system_messages
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
print(text.strip("\n"))
|
print(text.strip("\n"))
|
||||||
self._print_func(
|
self._print_func(
|
||||||
self._dividing_line.get_full_static_line(
|
self._dividing_line.get_full_static_line(is_override=self._override_system_messages)
|
||||||
is_override=self._override_system_messages
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -238,14 +221,10 @@ class BaseApp:
|
|||||||
"""
|
"""
|
||||||
input_command_trigger = command.trigger
|
input_command_trigger = command.trigger
|
||||||
if self._ignore_command_register:
|
if self._ignore_command_register:
|
||||||
if input_command_trigger.lower() in list(
|
if input_command_trigger.lower() in list(self._current_matching_triggers_with_routers.keys()):
|
||||||
self._current_matching_triggers_with_routers.keys()
|
|
||||||
):
|
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
if input_command_trigger in list(
|
if input_command_trigger in list(self._current_matching_triggers_with_routers.keys()):
|
||||||
self._current_matching_triggers_with_routers.keys()
|
|
||||||
):
|
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -303,9 +282,7 @@ class BaseApp:
|
|||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
self._prompt = f"[italic dim bold]{self._prompt}"
|
self._prompt = f"[italic dim bold]{self._prompt}"
|
||||||
self._initial_message = (
|
self._initial_message = "\n" + f"[bold red]{text2art(self._initial_message, font='tarty1')}" + "\n"
|
||||||
"\n" + f"[bold red]{text2art(self._initial_message, font='tarty1')}" + "\n"
|
|
||||||
)
|
|
||||||
self._farewell_message = (
|
self._farewell_message = (
|
||||||
"[bold red]\n\n"
|
"[bold red]\n\n"
|
||||||
+ str(text2art(self._farewell_message, font="chanky")) # pyright: ignore[reportUnknownArgumentType]
|
+ 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(
|
self._repeated_input_flags_handler = lambda raw_command: self._print_func(
|
||||||
f"[red bold]Repeated input flags: {escape(raw_command)}"
|
f"[red bold]Repeated input flags: {escape(raw_command)}"
|
||||||
)
|
)
|
||||||
self._empty_input_command_handler = lambda: self._print_func(
|
self._empty_input_command_handler = lambda: self._print_func("[red bold]Empty input command")
|
||||||
"[red bold]Empty input command"
|
|
||||||
)
|
|
||||||
|
|
||||||
def unknown_command_handler(command: InputCommand) -> None:
|
def unknown_command_handler(command: InputCommand) -> None:
|
||||||
cmd_trg: str = command.trigger
|
cmd_trg: str = command.trigger
|
||||||
mst_sim_cmd: str | None = self._most_similar_command(cmd_trg)
|
mst_sim_cmd: str | None = self._most_similar_command(cmd_trg)
|
||||||
first_part_of_text = (
|
first_part_of_text = f"[red]Unknown command:[/red] [blue]{escape(cmd_trg)}[/blue]"
|
||||||
f"[red]Unknown command:[/red] [blue]{escape(cmd_trg)}[/blue]"
|
|
||||||
)
|
|
||||||
second_part_of_text = (
|
second_part_of_text = (
|
||||||
("[red], most similar:[/red] " + ("[blue]" + mst_sim_cmd + "[/blue]"))
|
("[red], most similar:[/red] " + ("[blue]" + mst_sim_cmd + "[/blue]")) if mst_sim_cmd else ""
|
||||||
if mst_sim_cmd
|
|
||||||
else ""
|
|
||||||
)
|
)
|
||||||
self._print_func(first_part_of_text + second_part_of_text)
|
self._print_func(first_part_of_text + second_part_of_text)
|
||||||
|
|
||||||
@@ -356,13 +327,9 @@ class BaseApp:
|
|||||||
|
|
||||||
for trigger in combined:
|
for trigger in combined:
|
||||||
self._matching_default_triggers_with_routers[trigger] = router_entity
|
self._matching_default_triggers_with_routers[trigger] = router_entity
|
||||||
self._matching_lower_triggers_with_routers[trigger.lower()] = (
|
self._matching_lower_triggers_with_routers[trigger.lower()] = router_entity
|
||||||
router_entity
|
|
||||||
)
|
|
||||||
|
|
||||||
self._autocompleter.initial_setup(
|
self._autocompleter.initial_setup(list(self._current_matching_triggers_with_routers.keys()))
|
||||||
list(self._current_matching_triggers_with_routers.keys())
|
|
||||||
)
|
|
||||||
|
|
||||||
seen = {}
|
seen = {}
|
||||||
for item in list(self._current_matching_triggers_with_routers.keys()):
|
for item in list(self._current_matching_triggers_with_routers.keys()):
|
||||||
@@ -382,7 +349,7 @@ class BaseApp:
|
|||||||
self._print_func(message)
|
self._print_func(message)
|
||||||
if self._messages_on_startup:
|
if self._messages_on_startup:
|
||||||
print("\n")
|
print("\n")
|
||||||
if not self._repeat_command_groups_description:
|
if not self._repeat_command_groups_printing_description:
|
||||||
self._print_command_group_description()
|
self._print_command_group_description()
|
||||||
|
|
||||||
|
|
||||||
@@ -405,7 +372,7 @@ class App(BaseApp):
|
|||||||
system_router_title: str | None = "System points:",
|
system_router_title: str | None = "System points:",
|
||||||
ignore_command_register: bool = True,
|
ignore_command_register: bool = True,
|
||||||
dividing_line: AVAILABLE_DIVIDING_LINES = DEFAULT_DIVIDING_LINE,
|
dividing_line: AVAILABLE_DIVIDING_LINES = DEFAULT_DIVIDING_LINE,
|
||||||
repeat_command_groups: bool = True,
|
repeat_command_groups_printing: bool = True,
|
||||||
override_system_messages: bool = False,
|
override_system_messages: bool = False,
|
||||||
autocompleter: AutoCompleter = DEFAULT_AUTOCOMPLETER,
|
autocompleter: AutoCompleter = DEFAULT_AUTOCOMPLETER,
|
||||||
print_func: Printer = DEFAULT_PRINT_FUNC,
|
print_func: Printer = DEFAULT_PRINT_FUNC,
|
||||||
@@ -420,7 +387,7 @@ class App(BaseApp):
|
|||||||
:param system_router_title: system router title
|
:param system_router_title: system router title
|
||||||
:param ignore_command_register: whether to ignore the case of the entered commands
|
:param ignore_command_register: whether to ignore the case of the entered commands
|
||||||
:param dividing_line: the entity of the dividing line
|
: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 override_system_messages: whether to redefine the default formatting of system messages
|
||||||
:param autocompleter: the entity of the autocompleter
|
:param autocompleter: the entity of the autocompleter
|
||||||
:param print_func: system messages text output function
|
:param print_func: system messages text output function
|
||||||
@@ -434,7 +401,7 @@ class App(BaseApp):
|
|||||||
system_router_title=system_router_title,
|
system_router_title=system_router_title,
|
||||||
ignore_command_register=ignore_command_register,
|
ignore_command_register=ignore_command_register,
|
||||||
dividing_line=dividing_line,
|
dividing_line=dividing_line,
|
||||||
repeat_command_groups=repeat_command_groups,
|
repeat_command_groups_printing=repeat_command_groups_printing,
|
||||||
override_system_messages=override_system_messages,
|
override_system_messages=override_system_messages,
|
||||||
autocompleter=autocompleter,
|
autocompleter=autocompleter,
|
||||||
print_func=print_func,
|
print_func=print_func,
|
||||||
@@ -447,15 +414,13 @@ class App(BaseApp):
|
|||||||
"""
|
"""
|
||||||
self._pre_cycle_setup()
|
self._pre_cycle_setup()
|
||||||
while True:
|
while True:
|
||||||
if self._repeat_command_groups_description:
|
if self._repeat_command_groups_printing_description:
|
||||||
self._print_command_group_description()
|
self._print_command_group_description()
|
||||||
|
|
||||||
raw_command: str = Console().input(self._prompt)
|
raw_command: str = Console().input(self._prompt)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
input_command: InputCommand = InputCommand.parse(
|
input_command: InputCommand = InputCommand.parse(raw_command=raw_command)
|
||||||
raw_command=raw_command
|
|
||||||
)
|
|
||||||
except InputCommandException as error:
|
except InputCommandException as error:
|
||||||
with redirect_stdout(io.StringIO()) as stderr:
|
with redirect_stdout(io.StringIO()) as stderr:
|
||||||
self._error_handler(error, raw_command)
|
self._error_handler(error, raw_command)
|
||||||
@@ -466,8 +431,7 @@ class App(BaseApp):
|
|||||||
if self._is_exit_command(input_command):
|
if self._is_exit_command(input_command):
|
||||||
system_router.finds_appropriate_handler(input_command)
|
system_router.finds_appropriate_handler(input_command)
|
||||||
self._autocompleter.exit_setup(
|
self._autocompleter.exit_setup(
|
||||||
list(self._current_matching_triggers_with_routers.keys()),
|
list(self._current_matching_triggers_with_routers.keys()), self._ignore_command_register
|
||||||
self._ignore_command_register
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -478,24 +442,18 @@ class App(BaseApp):
|
|||||||
self._print_framed_text(stdout_res)
|
self._print_framed_text(stdout_res)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
processing_router = self._current_matching_triggers_with_routers[
|
processing_router = self._current_matching_triggers_with_routers[input_command.trigger.lower()]
|
||||||
input_command.trigger.lower()
|
|
||||||
]
|
|
||||||
|
|
||||||
if processing_router.disable_redirect_stdout:
|
if processing_router.disable_redirect_stdout:
|
||||||
dividing_line_unit_part: str = self._dividing_line.get_unit_part()
|
dividing_line_unit_part: str = self._dividing_line.get_unit_part()
|
||||||
self._print_func(
|
self._print_func(
|
||||||
StaticDividingLine(
|
StaticDividingLine(dividing_line_unit_part).get_full_static_line(
|
||||||
dividing_line_unit_part
|
|
||||||
).get_full_static_line(
|
|
||||||
is_override=self._override_system_messages
|
is_override=self._override_system_messages
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
processing_router.finds_appropriate_handler(input_command)
|
processing_router.finds_appropriate_handler(input_command)
|
||||||
self._print_func(
|
self._print_func(
|
||||||
StaticDividingLine(
|
StaticDividingLine(dividing_line_unit_part).get_full_static_line(
|
||||||
dividing_line_unit_part
|
|
||||||
).get_full_static_line(
|
|
||||||
is_override=self._override_system_messages
|
is_override=self._override_system_messages
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ __all__ = ["NonStandardBehaviorHandler", "EmptyCommandHandler", "Printer", "Desc
|
|||||||
|
|
||||||
from typing import Protocol, TypeVar
|
from typing import Protocol, TypeVar
|
||||||
|
|
||||||
T = TypeVar('T', contravariant=True) # noqa: WPS111
|
T = TypeVar("T", contravariant=True) # noqa: WPS111
|
||||||
|
|
||||||
|
|
||||||
class NonStandardBehaviorHandler(Protocol[T]):
|
class NonStandardBehaviorHandler(Protocol[T]):
|
||||||
def __call__(self, __param: T) -> None:
|
def __call__(self, __param: T) -> None:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class EmptyCommandHandler(Protocol):
|
class EmptyCommandHandler(Protocol):
|
||||||
def __call__(self) -> None:
|
def __call__(self) -> None:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
"InputCommandException",
|
"InputCommandException",
|
||||||
"UnprocessedInputFlagException",
|
"UnprocessedInputFlagException",
|
||||||
"RepeatedInputFlagsException",
|
"RepeatedInputFlagsException",
|
||||||
"EmptyInputCommandException",
|
"EmptyInputCommandException",
|
||||||
]
|
]
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
@@ -15,6 +15,7 @@ class InputCommandException(ABC, Exception):
|
|||||||
"""
|
"""
|
||||||
Private. Base exception class for all exceptions raised when parse input command
|
Private. Base exception class for all exceptions raised when parse input command
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
@@ -25,6 +26,7 @@ class UnprocessedInputFlagException(InputCommandException):
|
|||||||
"""
|
"""
|
||||||
Private. Raised when an unprocessed input flag is detected
|
Private. Raised when an unprocessed input flag is detected
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return "Unprocessed Input Flags"
|
return "Unprocessed Input Flags"
|
||||||
@@ -42,16 +44,14 @@ class RepeatedInputFlagsException(InputCommandException):
|
|||||||
@override
|
@override
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
string_entity: str = self.flag.string_entity
|
string_entity: str = self.flag.string_entity
|
||||||
return (
|
return f"Repeated Input Flags\nDuplicate flag was detected in the input: '{string_entity}'"
|
||||||
"Repeated Input Flags\n"
|
|
||||||
f"Duplicate flag was detected in the input: '{string_entity}'"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class EmptyInputCommandException(InputCommandException):
|
class EmptyInputCommandException(InputCommandException):
|
||||||
"""
|
"""
|
||||||
Private. Raised when an empty input command is detected
|
Private. Raised when an empty input command is detected
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return "Input Command is empty"
|
return "Input Command is empty"
|
||||||
|
|||||||
@@ -9,23 +9,21 @@ DEFAULT_PREFIX: Literal["-", "--", "---"] = "-"
|
|||||||
|
|
||||||
|
|
||||||
class PredefinedFlags:
|
class PredefinedFlags:
|
||||||
HELP = Flag(name="help", possible_values=PossibleValues.NEITHER)
|
HELP = Flag(name="help", possible_values=PossibleValues.NEITHER)
|
||||||
SHORT_HELP = Flag(name="H", prefix=DEFAULT_PREFIX, possible_values=PossibleValues.NEITHER)
|
SHORT_HELP = Flag(name="H", prefix=DEFAULT_PREFIX, possible_values=PossibleValues.NEITHER)
|
||||||
|
|
||||||
INFO = Flag(name="info", possible_values=PossibleValues.NEITHER) # noqa: WPS110
|
INFO = Flag(name="info", possible_values=PossibleValues.NEITHER) # noqa: WPS110
|
||||||
SHORT_INFO = Flag(name="I", prefix=DEFAULT_PREFIX, possible_values=PossibleValues.NEITHER)
|
SHORT_INFO = Flag(name="I", prefix=DEFAULT_PREFIX, possible_values=PossibleValues.NEITHER)
|
||||||
|
|
||||||
ALL = Flag(name="all", possible_values=PossibleValues.NEITHER)
|
ALL = Flag(name="all", possible_values=PossibleValues.NEITHER)
|
||||||
SHORT_ALL = Flag(name="A", prefix=DEFAULT_PREFIX, possible_values=PossibleValues.NEITHER)
|
SHORT_ALL = Flag(name="A", prefix=DEFAULT_PREFIX, possible_values=PossibleValues.NEITHER)
|
||||||
|
|
||||||
HOST = Flag(
|
HOST = Flag(name="host", possible_values=re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$"))
|
||||||
name="host", possible_values=re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
|
SHORT_HOST = Flag(
|
||||||
)
|
name="H",
|
||||||
SHORT_HOST = Flag(
|
prefix=DEFAULT_PREFIX,
|
||||||
name="H",
|
possible_values=re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$"),
|
||||||
prefix=DEFAULT_PREFIX,
|
)
|
||||||
possible_values=re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$"),
|
|
||||||
)
|
|
||||||
|
|
||||||
PORT = Flag(name="port", possible_values=re.compile(r"^\d{1,5}$"))
|
PORT = Flag(name="port", possible_values=re.compile(r"^\d{1,5}$"))
|
||||||
SHORT_PORT = Flag(name="P", prefix=DEFAULT_PREFIX, possible_values=re.compile(r"^\d{1,5}$"))
|
SHORT_PORT = Flag(name="P", prefix=DEFAULT_PREFIX, possible_values=re.compile(r"^\d{1,5}$"))
|
||||||
|
|||||||
@@ -6,19 +6,21 @@ from typing import Literal, override
|
|||||||
|
|
||||||
|
|
||||||
class PossibleValues(Enum):
|
class PossibleValues(Enum):
|
||||||
NEITHER = 'NEITHER'
|
NEITHER = "NEITHER"
|
||||||
ALL = 'ALL'
|
ALL = "ALL"
|
||||||
|
|
||||||
|
|
||||||
class ValidationStatus(Enum):
|
class ValidationStatus(Enum):
|
||||||
VALID = 'VALID'
|
VALID = "VALID"
|
||||||
INVALID = 'INVALID'
|
INVALID = "INVALID"
|
||||||
UNDEFINED = 'UNDEFINED'
|
UNDEFINED = "UNDEFINED"
|
||||||
|
|
||||||
|
|
||||||
class Flag:
|
class Flag:
|
||||||
def __init__(
|
def __init__(
|
||||||
self, name: str, *,
|
self,
|
||||||
|
name: str,
|
||||||
|
*,
|
||||||
prefix: Literal["-", "--", "---"] = "--",
|
prefix: Literal["-", "--", "---"] = "--",
|
||||||
possible_values: list[str] | Pattern[str] | PossibleValues = PossibleValues.ALL,
|
possible_values: list[str] | Pattern[str] | PossibleValues = PossibleValues.ALL,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -65,7 +67,7 @@ class Flag:
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'Flag<name={self.name}, prefix={self.prefix}>'
|
return f"Flag<name={self.name}, prefix={self.prefix}>"
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def __eq__(self, other: object) -> bool:
|
def __eq__(self, other: object) -> bool:
|
||||||
@@ -77,10 +79,12 @@ class Flag:
|
|||||||
|
|
||||||
class InputFlag:
|
class InputFlag:
|
||||||
def __init__(
|
def __init__(
|
||||||
self, name: str, *,
|
self,
|
||||||
prefix: Literal['-', '--', '---'] = '--',
|
name: str,
|
||||||
|
*,
|
||||||
|
prefix: Literal["-", "--", "---"] = "--",
|
||||||
input_value: str | None,
|
input_value: str | None,
|
||||||
status: ValidationStatus | None
|
status: ValidationStatus | None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Public. The entity of the flag of the entered command
|
Public. The entity of the flag of the entered command
|
||||||
@@ -90,7 +94,7 @@ class InputFlag:
|
|||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
self.name: str = name
|
self.name: str = name
|
||||||
self.prefix: Literal['-', '--', '---'] = prefix
|
self.prefix: Literal["-", "--", "---"] = prefix
|
||||||
self.input_value: str | None = input_value
|
self.input_value: str | None = input_value
|
||||||
self.status: ValidationStatus | None = status
|
self.status: ValidationStatus | None = status
|
||||||
|
|
||||||
@@ -105,17 +109,15 @@ class InputFlag:
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f'{self.string_entity} {self.input_value}'
|
return f"{self.string_entity} {self.input_value}"
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'InputFlag<name={self.name}, prefix={self.prefix}, value={self.input_value}, status={self.status}>'
|
return f"InputFlag<name={self.name}, prefix={self.prefix}, value={self.input_value}, status={self.status}>"
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def __eq__(self, other: object) -> bool:
|
def __eq__(self, other: object) -> bool:
|
||||||
if isinstance(other, InputFlag):
|
if isinstance(other, InputFlag):
|
||||||
return (
|
return self.name == other.name
|
||||||
self.name == other.name
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
__all__ = [
|
__all__ = ["Command", "InputCommand"]
|
||||||
"Command",
|
|
||||||
"InputCommand"
|
|
||||||
]
|
|
||||||
|
|
||||||
from typing import Literal, Never, Self, cast
|
from typing import Literal, Never, Self, cast
|
||||||
|
|
||||||
from argenta.command.exceptions import (EmptyInputCommandException,
|
from argenta.command.exceptions import (
|
||||||
RepeatedInputFlagsException,
|
EmptyInputCommandException,
|
||||||
UnprocessedInputFlagException)
|
RepeatedInputFlagsException,
|
||||||
|
UnprocessedInputFlagException,
|
||||||
|
)
|
||||||
from argenta.command.flag.flags.models import Flags, InputFlags
|
from argenta.command.flag.flags.models import Flags, InputFlags
|
||||||
from argenta.command.flag.models import Flag, InputFlag, ValidationStatus
|
from argenta.command.flag.models import Flag, InputFlag, ValidationStatus
|
||||||
|
|
||||||
@@ -23,7 +22,8 @@ DEFAULT_WITHOUT_INPUT_FLAGS: InputFlags = InputFlags()
|
|||||||
class Command:
|
class Command:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
trigger: str, *,
|
trigger: str,
|
||||||
|
*,
|
||||||
description: str | None = None,
|
description: str | None = None,
|
||||||
flags: Flag | Flags = DEFAULT_WITHOUT_FLAGS,
|
flags: Flag | Flags = DEFAULT_WITHOUT_FLAGS,
|
||||||
aliases: list[str] | None = None,
|
aliases: list[str] | None = None,
|
||||||
@@ -40,9 +40,7 @@ class Command:
|
|||||||
self.description: str = description if description else "Command without description"
|
self.description: str = description if description else "Command without description"
|
||||||
self.aliases: list[str] = aliases if aliases else []
|
self.aliases: list[str] = aliases if aliases else []
|
||||||
|
|
||||||
def validate_input_flag(
|
def validate_input_flag(self, flag: InputFlag) -> ValidationStatus:
|
||||||
self, flag: InputFlag
|
|
||||||
) -> ValidationStatus:
|
|
||||||
"""
|
"""
|
||||||
Private. Validates the input flag
|
Private. Validates the input flag
|
||||||
:param flag: input flag for validation
|
:param flag: input flag for validation
|
||||||
@@ -60,8 +58,7 @@ class Command:
|
|||||||
|
|
||||||
|
|
||||||
class InputCommand:
|
class InputCommand:
|
||||||
def __init__(self, trigger: str, *,
|
def __init__(self, trigger: str, *, input_flags: InputFlag | InputFlags = DEFAULT_WITHOUT_INPUT_FLAGS):
|
||||||
input_flags: InputFlag | InputFlags = DEFAULT_WITHOUT_INPUT_FLAGS):
|
|
||||||
"""
|
"""
|
||||||
Private. The model of the input command, after parsing
|
Private. The model of the input command, after parsing
|
||||||
:param trigger:the trigger of the command
|
:param trigger:the trigger of the command
|
||||||
@@ -69,7 +66,9 @@ class InputCommand:
|
|||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
self.trigger: str = trigger
|
self.trigger: str = trigger
|
||||||
self.input_flags: InputFlags = input_flags if isinstance(input_flags, InputFlags) else InputFlags([input_flags])
|
self.input_flags: InputFlags = (
|
||||||
|
input_flags if isinstance(input_flags, InputFlags) else InputFlags([input_flags])
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse(cls, raw_command: str) -> Self:
|
def parse(cls, raw_command: str) -> Self:
|
||||||
@@ -108,13 +107,13 @@ class CommandParser:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
input_flag = InputFlag(
|
input_flag = InputFlag(
|
||||||
name=crnt_flg_name[crnt_flg_name.rfind(MIN_FLAG_PREFIX) + 1:],
|
name=crnt_flg_name[crnt_flg_name.rfind(MIN_FLAG_PREFIX) + 1 :],
|
||||||
prefix=cast(
|
prefix=cast(
|
||||||
Literal["-", "--", "---"],
|
Literal["-", "--", "---"],
|
||||||
crnt_flg_name[:crnt_flg_name.rfind(MIN_FLAG_PREFIX) + 1],
|
crnt_flg_name[: crnt_flg_name.rfind(MIN_FLAG_PREFIX) + 1],
|
||||||
),
|
),
|
||||||
input_value=crnt_flg_val,
|
input_value=crnt_flg_val,
|
||||||
status=None
|
status=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
if input_flag in self._parsed_input_flags:
|
if input_flag in self._parsed_input_flags:
|
||||||
@@ -125,8 +124,7 @@ class CommandParser:
|
|||||||
|
|
||||||
return (self._parsed_input_flags, crnt_flg_name, crnt_flg_val)
|
return (self._parsed_input_flags, crnt_flg_name, crnt_flg_val)
|
||||||
|
|
||||||
def _is_next_token_value(self, current_index: int,
|
def _is_next_token_value(self, current_index: int, _tokens: list[str] | list[Never]) -> bool:
|
||||||
_tokens: list[str] | list[Never]) -> bool:
|
|
||||||
next_index = current_index + 1
|
next_index = current_index + 1
|
||||||
if next_index >= len(_tokens):
|
if next_index >= len(_tokens):
|
||||||
return False
|
return False
|
||||||
@@ -134,17 +132,16 @@ class CommandParser:
|
|||||||
next_token = _tokens[next_index]
|
next_token = _tokens[next_index]
|
||||||
return not next_token.startswith(MIN_FLAG_PREFIX)
|
return not next_token.startswith(MIN_FLAG_PREFIX)
|
||||||
|
|
||||||
|
|
||||||
def _parse_single_token(
|
def _parse_single_token(
|
||||||
token: str,
|
token: str, crnt_flag_name: str | None, crnt_flag_val: str | None
|
||||||
crnt_flag_name: str | None,
|
|
||||||
crnt_flag_val: str | None
|
|
||||||
) -> tuple[str | None, str | None]:
|
) -> tuple[str | None, str | None]:
|
||||||
if not token.startswith(MIN_FLAG_PREFIX):
|
if not token.startswith(MIN_FLAG_PREFIX):
|
||||||
if not crnt_flag_name or crnt_flag_val:
|
if not crnt_flag_name or crnt_flag_val:
|
||||||
raise UnprocessedInputFlagException
|
raise UnprocessedInputFlagException
|
||||||
return crnt_flag_name, token
|
return crnt_flag_name, token
|
||||||
|
|
||||||
prefix = token[:token.rfind(MIN_FLAG_PREFIX)]
|
prefix = token[: token.rfind(MIN_FLAG_PREFIX)]
|
||||||
if len(token) < 2 or len(prefix) > 2:
|
if len(token) < 2 or len(prefix) > 2:
|
||||||
raise UnprocessedInputFlagException
|
raise UnprocessedInputFlagException
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ from typing import Any, Callable, TypeVar
|
|||||||
from dishka import Container, FromDishka
|
from dishka import Container, FromDishka
|
||||||
from dishka.integrations.base import is_dishka_injected, wrap_injection
|
from dishka.integrations.base import is_dishka_injected, wrap_injection
|
||||||
|
|
||||||
from argenta.app import App
|
from argenta.app.models import App
|
||||||
from argenta.response import Response
|
from argenta.response.entity import Response
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
@@ -25,9 +25,7 @@ def setup_dishka(app: App, container: Container, *, auto_inject: bool = False) -
|
|||||||
Response.patch_by_container(container)
|
Response.patch_by_container(container)
|
||||||
|
|
||||||
|
|
||||||
def _get_container_from_response(
|
def _get_container_from_response(args: tuple[Any, ...], kwargs: dict[str, Any]) -> Container:
|
||||||
args: tuple[Any, ...], kwargs: dict[str, Any]
|
|
||||||
) -> Container:
|
|
||||||
for arg in args:
|
for arg in args:
|
||||||
if isinstance(arg, Response):
|
if isinstance(arg, Response):
|
||||||
if hasattr(arg, "_dishka_container"):
|
if hasattr(arg, "_dishka_container"):
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
'SystemProvider',
|
"SystemProvider",
|
||||||
]
|
]
|
||||||
|
|
||||||
from dishka import Provider, Scope, provide
|
from dishka import Provider, Scope, provide
|
||||||
@@ -10,13 +10,9 @@ from argenta.orchestrator.argparser.entity import ArgSpace
|
|||||||
|
|
||||||
|
|
||||||
class SystemProvider(Provider):
|
class SystemProvider(Provider):
|
||||||
def __init__(self, arg_parser: ArgParser):
|
|
||||||
super().__init__()
|
|
||||||
self._arg_parser: ArgParser = arg_parser
|
|
||||||
|
|
||||||
@provide(scope=Scope.APP)
|
@provide(scope=Scope.APP)
|
||||||
def get_argspace(self) -> ArgSpace:
|
def get_argspace(self, arg_parser: ArgParser) -> ArgSpace:
|
||||||
return self._arg_parser.parsed_argspace
|
return arg_parser.parsed_argspace
|
||||||
|
|
||||||
@provide(scope=Scope.APP)
|
@provide(scope=Scope.APP)
|
||||||
def get_data_bridge(self) -> DataBridge:
|
def get_data_bridge(self) -> DataBridge:
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
from argenta.metrics.main import \
|
from argenta.metrics.main import get_time_of_pre_cycle_setup as get_time_of_pre_cycle_setup
|
||||||
get_time_of_pre_cycle_setup as get_time_of_pre_cycle_setup
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
'get_time_of_pre_cycle_setup',
|
"get_time_of_pre_cycle_setup",
|
||||||
]
|
]
|
||||||
|
|
||||||
import io
|
import io
|
||||||
@@ -17,6 +17,6 @@ def get_time_of_pre_cycle_setup(app: App) -> float:
|
|||||||
"""
|
"""
|
||||||
start = time()
|
start = time()
|
||||||
with redirect_stdout(io.StringIO()):
|
with redirect_stdout(io.StringIO()):
|
||||||
app._pre_cycle_setup() # pyright: ignore[reportPrivateUsage]
|
app._pre_cycle_setup() # pyright: ignore[reportPrivateUsage]
|
||||||
end = time()
|
end = time()
|
||||||
return end - start
|
return end - start
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
from argenta.orchestrator.argparser.arguments import \
|
from argenta.orchestrator.argparser.arguments import BooleanArgument as BooleanArgument
|
||||||
BooleanArgument as BooleanArgument
|
from argenta.orchestrator.argparser.arguments import ValueArgument as ValueArgument
|
||||||
from argenta.orchestrator.argparser.arguments import \
|
|
||||||
ValueArgument as ValueArgument
|
|
||||||
from argenta.orchestrator.argparser.entity import ArgParser as ArgParser
|
from argenta.orchestrator.argparser.entity import ArgParser as ArgParser
|
||||||
from argenta.orchestrator.argparser.entity import ArgSpace as ArgSpace
|
from argenta.orchestrator.argparser.entity import ArgSpace as ArgSpace
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
from argenta.orchestrator.argparser.arguments.models import \
|
from argenta.orchestrator.argparser.arguments.models import BooleanArgument as BooleanArgument
|
||||||
BooleanArgument as BooleanArgument
|
from argenta.orchestrator.argparser.arguments.models import InputArgument as InputArgument
|
||||||
from argenta.orchestrator.argparser.arguments.models import \
|
from argenta.orchestrator.argparser.arguments.models import ValueArgument as ValueArgument
|
||||||
InputArgument as InputArgument
|
|
||||||
from argenta.orchestrator.argparser.arguments.models import \
|
|
||||||
ValueArgument as ValueArgument
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
__all__ = [
|
__all__ = ["BooleanArgument", "ValueArgument", "InputArgument"]
|
||||||
'BooleanArgument',
|
|
||||||
'ValueArgument',
|
|
||||||
'InputArgument'
|
|
||||||
]
|
|
||||||
|
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
@@ -11,10 +7,8 @@ class BaseArgument:
|
|||||||
"""
|
"""
|
||||||
Private. Base class for all arguments
|
Private. Base class for all arguments
|
||||||
"""
|
"""
|
||||||
def __init__(self, name: str, *,
|
|
||||||
help: str,
|
def __init__(self, name: str, *, help: str, is_deprecated: bool, prefix: Literal["-", "--", "---"]):
|
||||||
is_deprecated: bool,
|
|
||||||
prefix: Literal["-", "--", "---"]):
|
|
||||||
"""
|
"""
|
||||||
Public. Boolean argument, does not require a value
|
Public. Boolean argument, does not require a value
|
||||||
:param name: name of the argument
|
:param name: name of the argument
|
||||||
@@ -33,13 +27,17 @@ class BaseArgument:
|
|||||||
|
|
||||||
|
|
||||||
class ValueArgument(BaseArgument):
|
class ValueArgument(BaseArgument):
|
||||||
def __init__(self, name: str, *,
|
def __init__(
|
||||||
prefix: Literal["-", "--", "---"] = "--",
|
self,
|
||||||
help: str = "Help message for the value argument",
|
name: str,
|
||||||
possible_values: list[str] | None = None,
|
*,
|
||||||
default: str | None = None,
|
prefix: Literal["-", "--", "---"] = "--",
|
||||||
is_required: bool = False,
|
help: str = "Help message for the value argument",
|
||||||
is_deprecated: bool = False):
|
possible_values: list[str] | None = None,
|
||||||
|
default: str | None = None,
|
||||||
|
is_required: bool = False,
|
||||||
|
is_deprecated: bool = False,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Public. Value argument, must have the value
|
Public. Value argument, must have the value
|
||||||
:param name: name of the argument
|
:param name: name of the argument
|
||||||
@@ -58,10 +56,14 @@ class ValueArgument(BaseArgument):
|
|||||||
|
|
||||||
|
|
||||||
class BooleanArgument(BaseArgument):
|
class BooleanArgument(BaseArgument):
|
||||||
def __init__(self, name: str, *,
|
def __init__(
|
||||||
prefix: Literal["-", "--", "---"] = "--",
|
self,
|
||||||
help: str = "Help message for the boolean argument",
|
name: str,
|
||||||
is_deprecated: bool = False):
|
*,
|
||||||
|
prefix: Literal["-", "--", "---"] = "--",
|
||||||
|
help: str = "Help message for the boolean argument",
|
||||||
|
is_deprecated: bool = False,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Public. Boolean argument, does not require a value
|
Public. Boolean argument, does not require a value
|
||||||
:param name: name of the argument
|
:param name: name of the argument
|
||||||
@@ -74,9 +76,7 @@ class BooleanArgument(BaseArgument):
|
|||||||
|
|
||||||
|
|
||||||
class InputArgument:
|
class InputArgument:
|
||||||
def __init__(self, name: str,
|
def __init__(self, name: str, value: str | Literal[True], founder_class: type[BaseArgument]) -> None:
|
||||||
value: str | Literal[True],
|
|
||||||
founder_class: type[BaseArgument]) -> None:
|
|
||||||
self.name: str = name
|
self.name: str = name
|
||||||
self.value: str | Literal[True] = value
|
self.value: str | Literal[True] = value
|
||||||
self.founder_class: type[BaseArgument] = founder_class
|
self.founder_class: type[BaseArgument] = founder_class
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ __all__ = [
|
|||||||
from argparse import ArgumentParser, Namespace
|
from argparse import ArgumentParser, Namespace
|
||||||
from typing import Never, Self
|
from typing import Never, Self
|
||||||
|
|
||||||
from argenta.orchestrator.argparser.arguments.models import (BaseArgument,
|
from argenta.orchestrator.argparser.arguments.models import (
|
||||||
BooleanArgument,
|
BaseArgument,
|
||||||
InputArgument,
|
BooleanArgument,
|
||||||
ValueArgument)
|
InputArgument,
|
||||||
|
ValueArgument,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ArgSpace:
|
class ArgSpace:
|
||||||
@@ -17,16 +19,16 @@ class ArgSpace:
|
|||||||
self.all_arguments = all_arguments
|
self.all_arguments = all_arguments
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_namespace(cls, namespace: Namespace,
|
def from_namespace(
|
||||||
processed_args: list[ValueArgument | BooleanArgument]) -> Self:
|
cls, namespace: Namespace, processed_args: list[ValueArgument | BooleanArgument]
|
||||||
name_type_paired_args: dict[str, type[BaseArgument]] = {
|
) -> Self:
|
||||||
arg.name: type(arg)
|
name_type_paired_args: dict[str, type[BaseArgument]] = {arg.name: type(arg) for arg in processed_args}
|
||||||
for arg in processed_args
|
return cls(
|
||||||
}
|
[
|
||||||
return cls([InputArgument(name=name,
|
InputArgument(name=name, value=value, founder_class=name_type_paired_args[name])
|
||||||
value=value,
|
for name, value in vars(namespace).items()
|
||||||
founder_class=name_type_paired_args[name])
|
]
|
||||||
for name, value in vars(namespace).items()])
|
)
|
||||||
|
|
||||||
def get_by_name(self, name: str) -> InputArgument | None:
|
def get_by_name(self, name: str) -> InputArgument | None:
|
||||||
for arg in self.all_arguments:
|
for arg in self.all_arguments:
|
||||||
@@ -41,7 +43,8 @@ class ArgSpace:
|
|||||||
class ArgParser:
|
class ArgParser:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
processed_args: list[ValueArgument | BooleanArgument], *,
|
processed_args: list[ValueArgument | BooleanArgument],
|
||||||
|
*,
|
||||||
name: str = "Argenta",
|
name: str = "Argenta",
|
||||||
description: str = "Argenta available arguments",
|
description: str = "Argenta available arguments",
|
||||||
epilog: str = "github.com/koloideal/Argenta | made by kolo",
|
epilog: str = "github.com/koloideal/Argenta | made by kolo",
|
||||||
@@ -64,21 +67,23 @@ class ArgParser:
|
|||||||
self._register_args(processed_args)
|
self._register_args(processed_args)
|
||||||
|
|
||||||
def _parse_args(self) -> None:
|
def _parse_args(self) -> None:
|
||||||
self.parsed_argspace = ArgSpace.from_namespace(namespace=self._core.parse_args(),
|
self.parsed_argspace = ArgSpace.from_namespace(
|
||||||
processed_args=self.processed_args)
|
namespace=self._core.parse_args(), processed_args=self.processed_args
|
||||||
|
)
|
||||||
|
|
||||||
def _register_args(self, processed_args: list[ValueArgument | BooleanArgument]) -> None:
|
def _register_args(self, processed_args: list[ValueArgument | BooleanArgument]) -> None:
|
||||||
for arg in processed_args:
|
for arg in processed_args:
|
||||||
if isinstance(arg, BooleanArgument):
|
if isinstance(arg, BooleanArgument):
|
||||||
_ = self._core.add_argument(arg.string_entity,
|
_ = self._core.add_argument(
|
||||||
action=arg.action,
|
arg.string_entity, action=arg.action, help=arg.help, deprecated=arg.is_deprecated
|
||||||
help=arg.help,
|
)
|
||||||
deprecated=arg.is_deprecated)
|
|
||||||
else:
|
else:
|
||||||
_ = self._core.add_argument(arg.string_entity,
|
_ = self._core.add_argument(
|
||||||
action=arg.action,
|
arg.string_entity,
|
||||||
help=arg.help,
|
action=arg.action,
|
||||||
default=arg.default,
|
help=arg.help,
|
||||||
choices=arg.possible_values,
|
default=arg.default,
|
||||||
required=arg.is_required,
|
choices=arg.possible_values,
|
||||||
deprecated=arg.is_deprecated)
|
required=arg.is_required,
|
||||||
|
deprecated=arg.is_deprecated,
|
||||||
|
)
|
||||||
|
|||||||
@@ -11,9 +11,12 @@ DEFAULT_ARGPARSER: ArgParser = ArgParser(processed_args=[])
|
|||||||
|
|
||||||
|
|
||||||
class Orchestrator:
|
class Orchestrator:
|
||||||
def __init__(self, arg_parser: ArgParser = DEFAULT_ARGPARSER,
|
def __init__(
|
||||||
custom_providers: list[Provider] = [],
|
self,
|
||||||
auto_inject_handlers: bool = True):
|
arg_parser: ArgParser = DEFAULT_ARGPARSER,
|
||||||
|
custom_providers: list[Provider] = [],
|
||||||
|
auto_inject_handlers: bool = True,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Public. An orchestrator and configurator that defines the behavior of an integrated system, one level higher than the App
|
Public. An orchestrator and configurator that defines the behavior of an integrated system, one level higher than the App
|
||||||
:param arg_parser: Cmd argument parser and configurator at startup
|
:param arg_parser: Cmd argument parser and configurator at startup
|
||||||
@@ -31,7 +34,9 @@ class Orchestrator:
|
|||||||
:param app: a running application
|
:param app: a running application
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
container = make_container(SystemProvider(self._arg_parser), *self._custom_providers)
|
container = make_container(
|
||||||
|
SystemProvider(), *self._custom_providers, context={ArgParser: self._arg_parser}
|
||||||
|
)
|
||||||
setup_dishka(app, container, auto_inject=self._auto_inject_handlers)
|
setup_dishka(app, container, auto_inject=self._auto_inject_handlers)
|
||||||
|
|
||||||
app.run_polling()
|
app.run_polling()
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ class ResponseStatus(Enum):
|
|||||||
UNDEFINED_AND_INVALID_FLAGS = "UNDEFINED_AND_INVALID_FLAGS"
|
UNDEFINED_AND_INVALID_FLAGS = "UNDEFINED_AND_INVALID_FLAGS"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_flags(cls, *, has_invalid_value_flags: bool, has_undefined_flags: bool) -> 'ResponseStatus':
|
def from_flags(cls, *, has_invalid_value_flags: bool, has_undefined_flags: bool) -> "ResponseStatus":
|
||||||
key = (has_invalid_value_flags, has_undefined_flags)
|
key = (has_invalid_value_flags, has_undefined_flags)
|
||||||
status_map: dict[tuple[bool, bool], ResponseStatus] = {
|
status_map: dict[tuple[bool, bool], ResponseStatus] = {
|
||||||
(True, True): cls.UNDEFINED_AND_INVALID_FLAGS,
|
(True, True): cls.UNDEFINED_AND_INVALID_FLAGS,
|
||||||
(True, False): cls.INVALID_VALUE_FLAGS,
|
(True, False): cls.INVALID_VALUE_FLAGS,
|
||||||
(False, True): cls.UNDEFINED_FLAGS,
|
(False, True): cls.UNDEFINED_FLAGS,
|
||||||
(False, False): cls.ALL_FLAGS_VALID,
|
(False, False): cls.ALL_FLAGS_VALID,
|
||||||
}
|
}
|
||||||
return status_map[key]
|
return status_map[key]
|
||||||
|
|||||||
@@ -32,9 +32,7 @@ class CommandHandlers:
|
|||||||
Private. The model that unites all CommandHandler of the routers
|
Private. The model that unites all CommandHandler of the routers
|
||||||
:param command_handlers: list of CommandHandlers for register
|
:param command_handlers: list of CommandHandlers for register
|
||||||
"""
|
"""
|
||||||
self.command_handlers: list[CommandHandler] = (
|
self.command_handlers: list[CommandHandler] = command_handlers if command_handlers else []
|
||||||
command_handlers if command_handlers else []
|
|
||||||
)
|
|
||||||
|
|
||||||
def add_handler(self, command_handler: CommandHandler) -> None:
|
def add_handler(self, command_handler: CommandHandler) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
__all__ = ["Router"]
|
__all__ = ["Router"]
|
||||||
|
|
||||||
from inspect import (get_annotations, getfullargspec, getsourcefile,
|
from inspect import get_annotations, getfullargspec, getsourcefile, getsourcelines
|
||||||
getsourcelines)
|
|
||||||
from typing import Callable, TypeAlias
|
from typing import Callable, TypeAlias
|
||||||
|
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
@@ -10,11 +9,12 @@ from argenta.command import Command, InputCommand
|
|||||||
from argenta.command.flag import ValidationStatus
|
from argenta.command.flag import ValidationStatus
|
||||||
from argenta.command.flag.flags import Flags, InputFlags
|
from argenta.command.flag.flags import Flags, InputFlags
|
||||||
from argenta.response import Response, ResponseStatus
|
from argenta.response import Response, ResponseStatus
|
||||||
from argenta.router.command_handler.entity import (CommandHandler,
|
from argenta.router.command_handler.entity import CommandHandler, CommandHandlers
|
||||||
CommandHandlers)
|
from argenta.router.exceptions import (
|
||||||
from argenta.router.exceptions import (RepeatedFlagNameException,
|
RepeatedFlagNameException,
|
||||||
RequiredArgumentNotPassedException,
|
RequiredArgumentNotPassedException,
|
||||||
TriggerContainSpacesException)
|
TriggerContainSpacesException,
|
||||||
|
)
|
||||||
|
|
||||||
HandlerFunc: TypeAlias = Callable[..., None]
|
HandlerFunc: TypeAlias = Callable[..., None]
|
||||||
|
|
||||||
@@ -78,9 +78,7 @@ class Router:
|
|||||||
if input_command_name.lower() in handle_command.aliases:
|
if input_command_name.lower() in handle_command.aliases:
|
||||||
self.process_input_command(input_command_flags, command_handler)
|
self.process_input_command(input_command_flags, command_handler)
|
||||||
|
|
||||||
def process_input_command(
|
def process_input_command(self, input_command_flags: InputFlags, command_handler: CommandHandler) -> None:
|
||||||
self, input_command_flags: InputFlags, command_handler: CommandHandler
|
|
||||||
) -> None:
|
|
||||||
"""
|
"""
|
||||||
Private. Processes input command with the appropriate handler
|
Private. Processes input command with the appropriate handler
|
||||||
:param input_command_flags: input command flags as InputFlags
|
:param input_command_flags: input command flags as InputFlags
|
||||||
@@ -90,9 +88,7 @@ class Router:
|
|||||||
handle_command = command_handler.handled_command
|
handle_command = command_handler.handled_command
|
||||||
if handle_command.registered_flags.flags:
|
if handle_command.registered_flags.flags:
|
||||||
if input_command_flags.flags:
|
if input_command_flags.flags:
|
||||||
response: Response = _structuring_input_flags(
|
response: Response = _structuring_input_flags(handle_command, input_command_flags)
|
||||||
handle_command, input_command_flags
|
|
||||||
)
|
|
||||||
command_handler.handling(response)
|
command_handler.handling(response)
|
||||||
else:
|
else:
|
||||||
response = Response(ResponseStatus.ALL_FLAGS_VALID)
|
response = Response(ResponseStatus.ALL_FLAGS_VALID)
|
||||||
@@ -103,9 +99,7 @@ class Router:
|
|||||||
for input_flag in input_command_flags:
|
for input_flag in input_command_flags:
|
||||||
input_flag.status = ValidationStatus.UNDEFINED
|
input_flag.status = ValidationStatus.UNDEFINED
|
||||||
undefined_flags.add_flag(input_flag)
|
undefined_flags.add_flag(input_flag)
|
||||||
response = Response(
|
response = Response(ResponseStatus.UNDEFINED_FLAGS, input_flags=undefined_flags)
|
||||||
ResponseStatus.UNDEFINED_FLAGS, input_flags=undefined_flags
|
|
||||||
)
|
|
||||||
command_handler.handling(response)
|
command_handler.handling(response)
|
||||||
else:
|
else:
|
||||||
response = Response(ResponseStatus.ALL_FLAGS_VALID)
|
response = Response(ResponseStatus.ALL_FLAGS_VALID)
|
||||||
@@ -142,15 +136,11 @@ class CommandDecorator:
|
|||||||
|
|
||||||
def __call__(self, handler_func: Callable[..., None]) -> Callable[..., None]:
|
def __call__(self, handler_func: Callable[..., None]) -> Callable[..., None]:
|
||||||
_validate_func_args(handler_func)
|
_validate_func_args(handler_func)
|
||||||
self.router.command_handlers.add_handler(
|
self.router.command_handlers.add_handler(CommandHandler(handler_func, self.command))
|
||||||
CommandHandler(handler_func, self.command)
|
|
||||||
)
|
|
||||||
return handler_func
|
return handler_func
|
||||||
|
|
||||||
|
|
||||||
def _structuring_input_flags(
|
def _structuring_input_flags(handled_command: Command, input_flags: InputFlags) -> Response:
|
||||||
handled_command: Command, input_flags: InputFlags
|
|
||||||
) -> Response:
|
|
||||||
"""
|
"""
|
||||||
Private. Validates flags of input command
|
Private. Validates flags of input command
|
||||||
:param handled_command: entity of the handled command
|
:param handled_command: entity of the handled command
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
import polib
|
|
||||||
import deepl
|
|
||||||
import getopt
|
|
||||||
import sys
|
|
||||||
import re
|
|
||||||
|
|
||||||
DEEPL_API_TOKEN = 'ADD YOUR API KEY HERE!'
|
|
||||||
|
|
||||||
global argv
|
|
||||||
global opts
|
|
||||||
global args
|
|
||||||
|
|
||||||
argv = sys.argv[1:]
|
|
||||||
opts, args = getopt.getopt(argv, "f:l:")
|
|
||||||
|
|
||||||
def translate(text, lang):
|
|
||||||
# Define a dictionary to hold the mappings of tokens to placeholders
|
|
||||||
placeholders = {}
|
|
||||||
|
|
||||||
# Use a regular expression to find all the tokens
|
|
||||||
tokens = re.findall(r'%\((.*?)\)s', text)
|
|
||||||
|
|
||||||
# Replace each token with a unique placeholder
|
|
||||||
for i, token in enumerate(tokens):
|
|
||||||
placeholder = f'__PLACEHOLDER_{i}__'
|
|
||||||
placeholders[placeholder] = f'%({token})s'
|
|
||||||
text = text.replace(f'%({token})s', placeholder)
|
|
||||||
|
|
||||||
# Perform the translation
|
|
||||||
translator = deepl.Translator(DEEPL_API_TOKEN)
|
|
||||||
translated_text = str(translator.translate_text(text, target_lang=lang))
|
|
||||||
|
|
||||||
# Replace the placeholders back with the original tokens
|
|
||||||
for placeholder, token in placeholders.items():
|
|
||||||
translated_text = translated_text.replace(placeholder, token)
|
|
||||||
|
|
||||||
return translated_text
|
|
||||||
|
|
||||||
def get_filename():
|
|
||||||
# read arguments from command line
|
|
||||||
for opt, arg in opts:
|
|
||||||
if opt in ['-f']:
|
|
||||||
filename = arg
|
|
||||||
if not filename:
|
|
||||||
print('Please enter the filename of the PO file e.g. /directory/django.po:')
|
|
||||||
filename = input()
|
|
||||||
return filename
|
|
||||||
|
|
||||||
def get_target_language():
|
|
||||||
# read arguments from command line
|
|
||||||
for opt, arg in opts:
|
|
||||||
if opt in ['-l']:
|
|
||||||
lang = arg
|
|
||||||
if not lang:
|
|
||||||
print('Please enter two letter ISO language code e.g. DE:')
|
|
||||||
lang = input()
|
|
||||||
return lang
|
|
||||||
|
|
||||||
def process_file(filename, lang):
|
|
||||||
po = polib.pofile(filename)
|
|
||||||
for entry in po.untranslated_entries():
|
|
||||||
if not entry.msgstr:
|
|
||||||
print(entry.msgid)
|
|
||||||
print('translating...')
|
|
||||||
entry.msgstr = translate(entry.msgid, lang)
|
|
||||||
print(entry.msgstr)
|
|
||||||
print('\n')
|
|
||||||
po.save(filename)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
process_file(get_filename(), get_target_language())
|
|
||||||
Reference in New Issue
Block a user