diff --git a/docs/code_snippets/error_handling/snippet.py b/docs/code_snippets/error_handling/snippet.py index 63988e2..a270faa 100644 --- a/docs/code_snippets/error_handling/snippet.py +++ b/docs/code_snippets/error_handling/snippet.py @@ -1,9 +1,7 @@ from argenta import App - def empty_command_handler(): print("Empty command handler called") - app: App = App() app.set_empty_command_handler(empty_command_handler) diff --git a/docs/code_snippets/error_handling/snippet2.py b/docs/code_snippets/error_handling/snippet2.py index 08a7124..324ade7 100644 --- a/docs/code_snippets/error_handling/snippet2.py +++ b/docs/code_snippets/error_handling/snippet2.py @@ -1,9 +1,7 @@ from argenta import App - def incorrect_input_syntax_handler(raw_command: str): print(f"Incorrect input syntax for command: {raw_command}") - app: App = App() app.set_incorrect_input_syntax_handler(incorrect_input_syntax_handler) diff --git a/docs/code_snippets/error_handling/snippet3.py b/docs/code_snippets/error_handling/snippet3.py index b3a0f15..a5a1f61 100644 --- a/docs/code_snippets/error_handling/snippet3.py +++ b/docs/code_snippets/error_handling/snippet3.py @@ -1,9 +1,7 @@ from argenta import App - def repeated_input_flags_handler(raw_command: str): print(f"Repeated input flags: {raw_command}") - app: App = App() app.set_repeated_input_flags_handler(repeated_input_flags_handler) diff --git a/docs/code_snippets/error_handling/snippet4.py b/docs/code_snippets/error_handling/snippet4.py index 972eff9..9d6efdf 100644 --- a/docs/code_snippets/error_handling/snippet4.py +++ b/docs/code_snippets/error_handling/snippet4.py @@ -1,9 +1,7 @@ from argenta import App - def empty_command_handler(): print("Empty input command") - app: App = App() app.set_empty_command_handler(empty_command_handler) diff --git a/docs/code_snippets/error_handling/snippet5.py b/docs/code_snippets/error_handling/snippet5.py index 575ffab..2ea24fb 100644 --- a/docs/code_snippets/error_handling/snippet5.py +++ b/docs/code_snippets/error_handling/snippet5.py @@ -1,10 +1,8 @@ from argenta import App from argenta.command import InputCommand - def unknown_command_handler(command: InputCommand): print(f"Unknown input command with trigger: {command.trigger}") - app: App = App() app.set_unknown_command_handler(unknown_command_handler) diff --git a/docs/code_snippets/error_handling/snippet6.py b/docs/code_snippets/error_handling/snippet6.py index 0dcda57..b14f60b 100644 --- a/docs/code_snippets/error_handling/snippet6.py +++ b/docs/code_snippets/error_handling/snippet6.py @@ -1,9 +1,7 @@ from argenta import App, Response - def exit_command_handler(response: Response): print("Exit command handler") - app: App = App() app.set_exit_command_handler(exit_command_handler) diff --git a/docs/code_snippets/quickstart/simple_app.py b/docs/code_snippets/quickstart/simple_app.py index b31b281..83f8808 100644 --- a/docs/code_snippets/quickstart/simple_app.py +++ b/docs/code_snippets/quickstart/simple_app.py @@ -12,12 +12,13 @@ orchestrator = Orchestrator() # 2. Создание роутера для группировки команд main_router = Router(title="Основные команды") + # 3. Определение команды и её обработчика -@main_router.command(Command( - "hello", - description="Печатает приветственное сообщение", - flags=Flag("name") -)) +@main_router.command( + Command( + "hello", description="Печатает приветственное сообщение", flags=Flag("name") + ) +) def hello_handler(response: Response): """Этот обработчик будет вызван для команды 'hello'.""" name = response.input_flags.get_flag_by_name("name") @@ -26,6 +27,7 @@ def hello_handler(response: Response): else: print("Привет, мир!") + # 4. Подключение роутера к приложению app.include_router(main_router) diff --git a/docs/code_snippets/quickstart/task_manager/handlers.py b/docs/code_snippets/quickstart/task_manager/handlers.py index 2345645..9897654 100644 --- a/docs/code_snippets/quickstart/task_manager/handlers.py +++ b/docs/code_snippets/quickstart/task_manager/handlers.py @@ -1,8 +1,7 @@ from typing import cast from argenta import Command, Response, Router -from argenta.command import Flag, Flags -from argenta.command.flag.models import ValidationStatus +from argenta.command.flag import ValidationStatus, Flag, Flags from argenta.di import FromDishka from .repository import Priority, Task, TaskRepository @@ -24,14 +23,12 @@ router = Router(title="Task Manager") ) def add_task(response: Response, repo: FromDishka[TaskRepository]): description_flag = response.input_flags.get_flag_by_name("description") - - if not description_flag or not description_flag.input_value: + if not description_flag or not description_flag.status == ValidationStatus.VALID: print("Error: --description flag is required.") return - task_description = description_flag.input_value + task_description = description_flag.input_value or "" priority_flag = response.input_flags.get_flag_by_name("priority") - if priority_flag and priority_flag.status == ValidationStatus.VALID: priority_value = priority_flag.input_value else: diff --git a/docs/code_snippets/quickstart/task_manager/main.py b/docs/code_snippets/quickstart/task_manager/main.py index 09e758a..f44d139 100644 --- a/docs/code_snippets/quickstart/task_manager/main.py +++ b/docs/code_snippets/quickstart/task_manager/main.py @@ -3,16 +3,16 @@ from argenta import App, Orchestrator from .handlers import router from .provider import TaskProvider -# 1. Создаем экземпляр приложения +# 1. Создаем экземпляр приложения и оркестратора app = App( initial_message="Task Manager", prompt="Enter a command: ", ) +orchestrator = Orchestrator(custom_providers=[TaskProvider()]) # 2. Подключаем роутер с нашими командами app.include_router(router) -# 3. Создаем и запускаем оркестратор +# 3. Запускаем поллинг через оркестратор if __name__ == "__main__": - orchestrator = Orchestrator(custom_providers=[TaskProvider()]) orchestrator.start_polling(app) diff --git a/docs/code_snippets/response/data_sharing.py b/docs/code_snippets/response/data_sharing.py index 72356f3..f9a92eb 100644 --- a/docs/code_snippets/response/data_sharing.py +++ b/docs/code_snippets/response/data_sharing.py @@ -3,19 +3,23 @@ from argenta.command import Flag from argenta.di import FromDishka # 1. Создаём роутер -router = Router(title='Authentication') +router = Router(title="Authentication") + # 2. Определяем сервис и обработчики def authenticate_user(username: str) -> str: """Возвращает фиктивный токен для пользователя.""" return f"token_for_{username}" -@router.command(Command('login', flags=Flag('username'))) + +@router.command(Command("login", flags=Flag("username"))) def login_handler(response: Response, data_bridge: FromDishka[DataBridge]): """Обработчик для команды 'login'. Сохраняет токен в хранилище.""" - username_flag = response.input_flags.get_flag_by_name('username') + username_flag = response.input_flags.get_flag_by_name("username") if not username_flag or not username_flag.input_value: - print("[red]Ошибка:[/red] необходимо указать имя пользователя с помощью флага --username.") + print( + "[red]Ошибка:[/red] необходимо указать имя пользователя с помощью флага --username." + ) return username = username_flag.input_value @@ -25,19 +29,23 @@ def login_handler(response: Response, data_bridge: FromDishka[DataBridge]): data_bridge.update({"auth_token": token}) print(f"[green]Успешный вход![/green] Пользователь '{username}' аутентифицирован.") -@router.command('get-profile') + +@router.command("get-profile") def get_profile_handler(response: Response, data_bridge: FromDishka[DataBridge]): """Обработчик для команды 'get-profile'. Использует токен из хранилища.""" session_data = data_bridge.get_all() token = session_data.get("auth_token") if not token: - print("[red]Ошибка:[/red] вы не аутентифицированы. Сначала выполните команду 'login'.") + print( + "[red]Ошибка:[/red] вы не аутентифицированы. Сначала выполните команду 'login'." + ) return print(f"Загрузка профиля с использованием токена: [yellow]{token}[/yellow]") -@router.command('logout') + +@router.command("logout") def logout_handler(response: Response, data_bridge: FromDishka[DataBridge]): """Обработчик для команды 'logout'. Очищает токен.""" try: @@ -45,4 +53,3 @@ def logout_handler(response: Response, data_bridge: FromDishka[DataBridge]): print("[green]Выход выполнен.[/green] Данные сессии очищены.") except KeyError: print("Вы и так не были аутентифицированы.") - diff --git a/docs/code_snippets/testing/app_integration_unittest.py b/docs/code_snippets/testing/app_integration_unittest.py index c4638a9..4f1ef30 100644 --- a/docs/code_snippets/testing/app_integration_unittest.py +++ b/docs/code_snippets/testing/app_integration_unittest.py @@ -14,6 +14,7 @@ class TestAppIntegration(unittest.TestCase): @self.router.command(Command("HELP", description="Show help")) def help_cmd(response: Response): print("Available commands: HELP") + _ = help_cmd # appease linter: function is registered via decorator self.app.include_router(self.router) @@ -22,5 +23,3 @@ class TestAppIntegration(unittest.TestCase): with redirect_stdout(io.StringIO()) as stdout: self.router.finds_appropriate_handler(InputCommand.parse("HELP")) self.assertIn("Available commands:", stdout.getvalue()) - - diff --git a/docs/code_snippets/testing/di_handler_unittest.py b/docs/code_snippets/testing/di_handler_unittest.py index 789ee96..63ff89f 100644 --- a/docs/code_snippets/testing/di_handler_unittest.py +++ b/docs/code_snippets/testing/di_handler_unittest.py @@ -15,6 +15,7 @@ class Service: def hello(self) -> str: return "world" + router = Router(title="DI") @@ -56,5 +57,3 @@ class TestDIHandler(unittest.TestCase): class _FakeApp: # Minimal stub for setup_dishka; app object is not used in unit tests registered_routers = [] - - diff --git a/docs/code_snippets/testing/simple_handler_unittest.py b/docs/code_snippets/testing/simple_handler_unittest.py index 81a5e04..3d0c835 100644 --- a/docs/code_snippets/testing/simple_handler_unittest.py +++ b/docs/code_snippets/testing/simple_handler_unittest.py @@ -20,5 +20,3 @@ class TestSimpleHandler(unittest.TestCase): with redirect_stdout(io.StringIO()) as stdout: router.finds_appropriate_handler(InputCommand.parse("PING")) self.assertIn("PONG", stdout.getvalue()) - - diff --git a/docs/index.rst b/docs/index.rst index ef5a4c9..d600571 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,13 +6,16 @@ Argenta ======= +Что это и зачем? +---------------- + **Библиотека для построения модульных CLI-приложений с простым и приятным API.** Если у вас есть функциональность, которую вы хотите предоставить в виде CLI-приложения, Argenta поможет вам в этом. Основная цель библиотеки — дать разработчикам возможность сосредоточиться на реализации своих идей, предоставляя для этого удобные абстракции. .. image:: https://github.com/koloideal/Argenta/blob/main/imgs/mock_app_preview4.png?raw=True - :alt: Пример приложения + :alt: App example Argenta предназначена для создания приложений, работающих в собственном контексте (scope). Это означает, что при запуске пользователь входит в интерактивную сессию, где ему доступна вся реализованная вами функциональность. @@ -22,9 +25,11 @@ Argenta предназначена для создания приложений, * **Интерактивные сессии**. В отличие от традиционных CLI-инструментов, Argenta создаёт циклические сессии, позволяя пользователю выполнять команды последовательно, не перезапуская приложение. * **Декларативный синтаксис**. Команды и их обработчики объявляются с помощью простых декораторов, что делает код чистым и интуитивно понятным. -* **Встроенное внедрение зависимостей (DI)**. Благодаря интеграции с `dishka `_, вы можете легко внедрять сервисы (например, подключения к БД) прямо в обработчики команд, что упрощает их тестирование и переиспользование. +* **Нативный DI**. Благодаря интеграции с `dishka `_, вы можете легко внедрять зависимости прямо в обработчики команд, что упрощает их тестирование и переиспользование. * **Автоматическая валидация и парсинг**. Библиотека берёт на себя обработку флагов и аргументов командной строки, включая их парсинг, валидацию и преобразование типов. -* **Гибкая настройка**. Вы можете легко кастомизировать системные сообщения, форматирование вывода и даже перенаправлять стандартный вывод (stdout) в свои обработчики. +* **Гибкая настройка**. Вы можете легко кастомизировать системные сообщения, форматирование вывода и т.д. + +----- Архитектура и жизненный цикл ----------------------------- diff --git a/docs/root/quickstart.rst b/docs/root/quickstart.rst index ee32893..30df561 100644 --- a/docs/root/quickstart.rst +++ b/docs/root/quickstart.rst @@ -5,12 +5,18 @@ В этом руководстве мы рассмотрим два примера создания CLI-приложения с помощью Argenta: -* **Простой пример**: Быстрое знакомство с основными компонентами, такими как `App`, `Command` и `Router`. +* **Простой пример**: Минимальное приложение, быстрое знакомство с основными компонентами. * **Более сложный пример**: Полнофункциональное приложение «Менеджер задач» с внедрением зависимостей и бизнес-логикой. Простой пример --------------- +**Установка** + +.. code-block:: shell + + pip install argenta + Этот пример демонстрирует абсолютный минимум, необходимый для создания и запуска приложения. Вы можете скопировать этот код, запустить его и сразу увидеть результат. .. literalinclude:: ../code_snippets/quickstart/simple_app.py @@ -21,6 +27,8 @@ .. image:: https://i.ibb.co/JwK9Vv4j/2025-11-03-135118.png :alt: Simple App Example + +----- Более сложный пример: Менеджер задач -------------------------------------- @@ -43,7 +51,7 @@ 3. **Создание провайдера для DI** -Чтобы Argenta могла внедрять `TaskRepository` в наши обработчики, мы создадим провайдер для `dishka`. +Чтобы Argenta могла внедрять ``TaskRepository`` в наши обработчики, мы создадим провайдер для ``dishka``. .. literalinclude:: ../code_snippets/quickstart/task_manager/provider.py :language: python @@ -51,7 +59,7 @@ 4. **Создание обработчиков команд** -Теперь создадим обработчики для команд `add-task` и `list-tasks`. Обратите внимание, как мы используем флаги и внедряем `TaskRepository`. +Теперь создадим обработчики для команд ``add-task`` и ``list-tasks``. Обратите внимание, как мы используем флаги и внедряем ``TaskRepository``. .. literalinclude:: ../code_snippets/quickstart/task_manager/handlers.py :language: python @@ -59,7 +67,7 @@ 5. **Сборка и запуск приложения** -Наконец, соберем все вместе: создадим экземпляр `App`, подключим роутер и провайдер, а затем запустим приложение. +Наконец, соберем все вместе: создадим экземпляр ``App``, подключим роутер и провайдер, а затем запустим приложение. .. literalinclude:: ../code_snippets/quickstart/task_manager/main.py :language: python @@ -67,7 +75,7 @@ 6. **Результат** -Теперь вы можете запустить `main.py` и взаимодействовать с вашим новым CLI-приложением. +Теперь вы можете запустить ``main.py`` и взаимодействовать с вашим новым CLI-приложением. .. image:: https://i.ibb.co/bgsCLZhP/image.png :alt: Task Manager Example diff --git a/translator.py b/translator.py new file mode 100644 index 0000000..e0d73ef --- /dev/null +++ b/translator.py @@ -0,0 +1,71 @@ +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()) \ No newline at end of file