diff --git a/docs/code_snippets/autocompleter_example_sample.py b/docs/code_snippets/autocompleter_example_sample.py new file mode 100644 index 0000000..c9dc4da --- /dev/null +++ b/docs/code_snippets/autocompleter_example_sample.py @@ -0,0 +1,10 @@ +from argenta import App +from argenta.app.autocompleter import AutoCompleter + +# Настройка автодополнения с сохранением истории в файл +my_autocompleter = AutoCompleter(history_filename="argenta_history.txt") + +# Передача настроенного автокомплитера в приложение +app = App(autocompleter=my_autocompleter) + +# ... остальная логика приложения \ No newline at end of file diff --git a/docs/root/api/app/autocompleter.rst b/docs/root/api/app/autocompleter.rst index da24e19..3ee1e0b 100644 --- a/docs/root/api/app/autocompleter.rst +++ b/docs/root/api/app/autocompleter.rst @@ -1,4 +1,51 @@ .. _root_api_app_autocompleter: -Autocompleter -**************** +AutoCompleter +===================== + +Объект ``AutoCompleter`` является компонентом ``Argenta``, отвечающим за интерактивное автодополнение команд. Его основная задача — улучшить опыт взаимодействия пользователя с командной строкой, предоставляя подсказки и автоматически завершая ввод на основе ранее введенных команд. Это значительно ускоряет работу и снижает вероятность опечаток. + +``AutoCompleter`` использует ``pyreadline3`` для имплементации ``readline GNU`` на ``Windows`` для управления историей команд и реализации логики автодополнения. + +----- + +Инициализация +------------- + +.. code:: python + + __init__(self, history_filename: str | None = None, + autocomplete_button: str = "tab") -> None + +Создает и настраивает экземпляр ``AutoCompleter``. + +* ``history_filename``: Имя файла для сохранения и загрузки истории команд. Если указано, история будет персистентной между сессиями приложения. Если **None**, история будет храниться только в памяти текущей сессии. +* ``autocomplete_button``: Название клавиши, которая активирует автодополнение. По умолчанию используется клавиша **"tab"**. + +----- + +Назначение и возможности +------------------------- + +``AutoCompleter`` обладает следующими возможностями: + +* **Автодополнение по истории**: Основная логика автодополнения основана на истории команд, которые пользователь вводил ранее. Когда пользователь начинает вводить команду и нажимает клавишу автодополнения (по умолчанию **Tab**), система ищет в истории все команды, начинающиеся с введенного текста. + +* **Завершение по общему префиксу**: Если найдено несколько команд с общим префиксом, автодокомплитер подставит только общую часть. Например, если в истории есть команды ``show_users`` и ``show_profile``, при вводе ``sho`` и нажатии **Tab** будет подставлено ``show_``. + +* **Персистентная история**: При указании параметра ``history_filename`` в конструкторе, вся история команд сохраняется в файл при выходе из приложения и загружается при следующем запуске. Это делает автодополнение со временем все более "умным" и персонализированным. + +* **Очистка истории**: При сохранении истории ``AutoCompleter`` автоматически удаляет дубликаты и команды, которые больше не зарегистрированы в приложении, поддерживая актуальность и чистоту файла истории. + +* **Настройка клавиши активации**: Вы можете изменить клавишу, отвечающую за автодополнение, через параметр ``autocomplete_button``. + +----- + +Пример использования +-------------------- + +``AutoCompleter`` передается как аргумент при инициализации `App`. + +.. literalinclude:: ../../../code_snippets/autocompleter_example_sample.py + :language: python + :linenos: diff --git a/docs/root/api/app/dividing_lines.rst b/docs/root/api/app/dividing_lines.rst index fe1895a..f7e644d 100644 --- a/docs/root/api/app/dividing_lines.rst +++ b/docs/root/api/app/dividing_lines.rst @@ -1,4 +1,81 @@ .. _root_api_app_dividing_lines: -DividingLines -**************** +Dividing Lines +============== + +Разделительные линии в ``Argenta`` играют важную роль в визуальном оформлении консольного интерфейса. Они используются для структурирования вывода, отделения блоков информации друг от друга (например, вывода команды от следующего приглашения к вводу). +Библиотека предлагает два подхода к управлению разделительными линиями: статический и динамический, каждый из которых имеет свои преимущества и сценарии использования. + +---- + +Класс ``StaticDividingLine`` +-------------------------- + +``StaticDividingLine`` — это класс, который создает разделительную линию **фиксированной** длины. Длина и символ-заполнитель задаются при инициализации объекта. Этот тип линии полезен, когда вам нужен предсказуемый и унифицированный внешний вид интерфейса, независимо от содержимого вывода. + +.. code-block:: python + :linenos: + + def __init__(self, unit_part: str = "-", *, + length: int = 25) -> None + +Создает экземпляр статической разделительной линии. + +* ``unit_part``: Символ, который будет использоваться для построения линии. Учитывается только первый символ строки. По умолчанию: ``-``. +* ``length``: Целое число, определяющее фиксированную длину линии в символах. Является keyword-only аргументом. По умолчанию: ``25``. + +----- + +Класс ``DynamicDividingLine`` +--------------------------- + +``DynamicDividingLine`` представляет собой более "умный" подход. Этот класс создает линию, длина которой **динамически** подстраивается под самую длинную строку, выведенную в консоль, в рамках выполнения одной команды. Это достигается за счет механизма перехвата `stdout`. В результате разделительные линии всегда идеально обрамляют выводимый контент, что выглядит очень аккуратно. + +.. code-block:: python + :linenos: + + __init__(self, unit_part: str = "-") -> None + +Создает экземпляр динамической разделительной линии. + +* ``unit_part``: Символ, который будет использоваться для построения линии. По умолчанию: ``-``. + +Длина для этой линии не задается при инициализации, так как она вычисляется автоматически во время выполнения. + +.. warning:: + Обязательно почитайте про нюансы использования динамических линий и перехвата ``stdout`` в :ref:`этом разделе`. + +Назначение и использование +-------------------------- + +Выбор между статической и динамической линией зависит от ваших потребностей в конкретном приложении или даже для конкретного `Router`. + +* **`StaticDividingLine`** идеально подходит, если: + * Вам нужен строгий, консистентный дизайн. + * Вы используете роутеры с отключенным перехватом `stdout` (`disable_redirect_stdout=True`), так как в этом случае динамическое вычисление длины невозможно. + +* **`DynamicDividingLine`** (используется по умолчанию) является предпочтительным выбором, если: + * Вы хотите, чтобы интерфейс выглядел аккуратно и адаптивно. + * Вывод ваших команд имеет разную длину, и вы хотите, чтобы рамки всегда соответствовали контенту. + +Тип разделительной линии для всего приложения задается при инициализации `App` через параметр `dividing_line`. + +Пример конфигурации +-------------------- + +.. code-block:: python + + from argenta.app import App + from argenta.app.dividing_line.models import StaticDividingLine, DynamicDividingLine + + # Создание статической линии из символов "=" длиной 40 + static_line = StaticDividingLine(unit_part="=", length=40) + + # Создание динамической линии из символов "*" + dynamic_line = DynamicDividingLine(unit_part="*") + + # Приложение со статической линией + app_with_static_line = App(dividing_line=static_line) + + # Приложение с динамической линией (поведение по умолчанию, но с кастомным символом) + app_with_dynamic_line = App(dividing_line=dynamic_line) diff --git a/docs/root/api/app/index.rst b/docs/root/api/app/index.rst index 1b5efc8..8bc521d 100644 --- a/docs/root/api/app/index.rst +++ b/docs/root/api/app/index.rst @@ -10,7 +10,8 @@ App Инициализация ------------- -.. code:: rust +.. code-block:: rust + :linenos: AVAILABLE_DIVIDING_LINES: TypeAlias = StaticDividingLine | DynamicDividingLine DEFAULT_DIVIDING_LINE: StaticDividingLine = StaticDividingLine() @@ -19,7 +20,8 @@ App DEFAULT_AUTOCOMPLETER: AutoCompleter = AutoCompleter() DEFAULT_EXIT_COMMAND: Command = Command("Q", description="Exit command") -.. code:: python +.. code-block:: python + :linenos: def __init__(self, *, prompt: str = "What do you want to do?\n\n", initial_message: str = "Argenta\n", @@ -33,7 +35,7 @@ App autocompleter: AutoCompleter = DEFAULT_AUTOCOMPLETER, print_func: Printer = DEFAULT_PRINT_FUNC) -> None -Создает и настраивает экземпляр приложения `Argenta`. +Создает и настраивает экземпляр приложения ``Argenta``. * ``prompt``: Строка-приглашение, которая отображается перед вводом каждой команды. По умолчанию: **"What do you want to do?\\n\\n"**. * ``initial_message``: Приветственное сообщение, которое выводится при запуске приложения. @@ -47,55 +49,98 @@ App * ``autocompleter``: Объект, отвечающий за логику автодополнения команд. * ``print_func``: Функция, используемая для вывода всех системных сообщений. По умолчанию используется ``rich.console.Console().print``. +----- + Основные методы --------------- -.. py:method:: include_router(self, router: Router) -> None +- .. py:method:: include_router(self, router: Router) -> None - Регистрирует один `Router` в приложении. Все команды, определенные в этом роутере, становятся доступными для вызова. + Регистрирует один ``Router`` в приложении. Все команды, определенные в этом роутере, становятся доступными для вызова. + + :param router: Объект роутера, который нужно зарегистрировать. - :param router: Объект роутера, который нужно зарегистрировать. +- .. py:method:: include_routers(self, *routers: Router) -> None -.. py:method:: include_routers(self, *routers: Router) -> None + Регистрирует несколько роутеров одновременно. Является удобной оберткой над ``include_router``. + + :param routers: Последовательность объектов ``Router`` для регистрации. - Регистрирует несколько роутеров одновременно. Является удобной оберткой над `include_router`. - - :param routers: Последовательность объектов `Router` для регистрации. - -.. py:method:: add_message_on_startup(self, message: str) -> None - - Добавляет дополнительное текстовое сообщение, которое будет выведено на экран при запуске приложения, сразу после `initial_message`. - - :param message: Строка с сообщением. +- .. py:method:: add_message_on_startup(self, message: str) -> None + + Добавляет дополнительное текстовое сообщение, которое будет выведено на экран при запуске приложения, сразу после `initial_message`. + + :param message: Строка с сообщением. + +----- Методы установки обработчиков ------------------------------- -`App` позволяет гибко настраивать реакцию на различные события, такие как ошибки ввода или ввод неизвестной команды. +``App`` позволяет гибко настраивать реакцию на различные события, такие как ошибки ввода или ввод неизвестной команды. + +.. hint:: + Подробнее о исключениях и их обработке в соответствующем :ref:`разделе документации `. + +----- .. py:method:: set_description_message_pattern(self, handler: DescriptionMessageGenerator) -> None Устанавливает пользовательский шаблон для форматирования строки, описывающей доступную команду (триггер + описание). + + ``DescriptionMessageGenerator`` -> ``Callable[[str, str], str]`` + + Где первый аргумент - это триггер команды, а второй - ее описание, возвращает строку, которая является форматированной строкой. + +------ .. py:method:: set_incorrect_input_syntax_handler(self, handler: NonStandardBehaviorHandler[str]) -> None Устанавливает обработчик, который вызывается при некорректном синтаксисе флагов в введенной команде. + + ``NonStandardBehaviorHandler[str]`` -> ``Callable[[str], None]`` + + Где первый и единственный аргумент - это необработанная строка пользовательского ввода. + +------ .. py:method:: set_repeated_input_flags_handler(self, handler: NonStandardBehaviorHandler[str]) -> None Устанавливает обработчик для ситуации, когда пользователь вводит один и тот же флаг несколько раз. + + ``NonStandardBehaviorHandler[str]`` -> ``Callable[[str], None]`` + + Где первый и единственный аргумент - это необработанная строка пользовательского ввода. + +------ .. py:method:: set_unknown_command_handler(self, handler: NonStandardBehaviorHandler[InputCommand]) -> None Устанавливает обработчик, который срабатывает, если введенная команда не была найдена ни в одном из зарегистрированных роутеров. + + ``NonStandardBehaviorHandler[InputCommand]`` -> ``Callable[[InputCommand], None]`` + + Где первый и единственный аргумент - это распаршенный в объект InputCommand ввод пользователя. + +----- .. py:method:: set_empty_command_handler(self, handler: EmptyCommandHandler) -> None Устанавливает обработчик для случая, когда пользователь отправляет пустую строку вместо команды. + + ``EmptyCommandHandler`` -> ``Callable[[], None]`` + + Не принимает и не возвращает ничего. + +----- .. py:method:: set_exit_command_handler(self, handler: NonStandardBehaviorHandler[Response]) -> None - Позволяет переопределить стандартное поведение при вызове команды выхода. По умолчанию просто выводится `farewell_message`. + Позволяет переопределить стандартное поведение при вызове команды выхода. По умолчанию просто выводится ``farewell_message``. + + ``NonStandardBehaviorHandler[Response]`` -> ``Callable[[Response], None]`` + + Где первый и единственный аргумент - это объект ответа, ``Response``. .. toctree:: :hidden: diff --git a/docs/root/dependency_injection.rst b/docs/root/dependency_injection.rst index 2417a3d..e3e3d44 100644 --- a/docs/root/dependency_injection.rst +++ b/docs/root/dependency_injection.rst @@ -20,16 +20,19 @@ .. literalinclude:: ../code_snippets/dependency_injection_example_sample.py :language: python + :linenos: ``Argenta`` -> ``dishka`` зарезолвит тайпхинты и внедрит зависимость с возвращаемым типом ``Connection``, прежде чем использовать зависимость её нужно создать, для этого нужно создать соответствующий провайдер. .. literalinclude:: ../code_snippets/dependency_injection_example_sample2.py :language: python + :linenos: После создания провайдера, его нужно зарегистрировать в оркестраторе. .. literalinclude:: ../code_snippets/dependency_injection_example_sample3.py :language: python + :linenos: Как это работает? ----------------- @@ -48,6 +51,7 @@ .. literalinclude:: ../code_snippets/dependency_injection_example_sample4.py :language: python + :linenos: Обмен данными между хендлерами ------------------------------ diff --git a/docs/root/error_handling.rst b/docs/root/error_handling.rst index abf9353..aa333d8 100644 --- a/docs/root/error_handling.rst +++ b/docs/root/error_handling.rst @@ -18,7 +18,8 @@ пустой команды .. literalinclude:: ../code_snippets/error_handling_example_sample.py - :language: python + :language: python + :linenos: .. _possible_errors: @@ -47,6 +48,7 @@ .. literalinclude:: ../code_snippets/error_handling_example_sample2.py :language: python + :linenos: --------------- @@ -72,7 +74,8 @@ Сэмпл кода, переопределяющего хэндлер ввода команды с повторяющимися флагами: .. literalinclude:: ../code_snippets/error_handling_example_sample3.py - :language: python + :language: python + :linenos: --------------- @@ -95,6 +98,7 @@ .. literalinclude:: ../code_snippets/error_handling_example_sample4.py :language: python + :linenos: --------------- @@ -118,6 +122,7 @@ .. literalinclude:: ../code_snippets/error_handling_example_sample5.py :language: python + :linenos: --------------- @@ -140,3 +145,4 @@ .. literalinclude:: ../code_snippets/error_handling_example_sample6.py :language: python + :linenos: diff --git a/docs/root/flags.rst b/docs/root/flags.rst index 52f0c69..0be338e 100644 --- a/docs/root/flags.rst +++ b/docs/root/flags.rst @@ -5,6 +5,8 @@ Флаги (или параметры) — это специальные аргументы, которые конечный юзер может добавлять к командам, чтобы управлять их поведением. +----- + Синтаксис флагов ----------------- @@ -17,6 +19,8 @@ То есть, у флага обязательно должен быть префикс, который может быть одним, двум или трем минусам. После префикса следует имя флага, без пробела, после, через пробел, идёт значение флага, если оно есть. +----- + Два типа флагов --------------- @@ -27,6 +31,8 @@ При регистрации флага вы можете указать допустимые для него значения, по умолчанию любое введённое значение для флага будет валидным. Допустимые значения можно указать различными способами: +----- + Ограничение по списку возможных значений ---------------------------------------- @@ -37,12 +43,15 @@ Предположим, у вас есть флаг ``--format``, который может принимать только значения ``json`` или ``xml``. .. code-block:: bash + :linenos: # Эта команда сработает export --format json # А эта вызовет ошибку валидации, так как "csv" нет в списке разрешённых export --format csv + +----- Проверка с помощью регулярных выражений ----------------------------------------- @@ -54,6 +63,7 @@ Допустим, флаг ``--email`` должен принимать только корректные email-адреса. .. code-block:: bash + :linenos: # Сработает, так как значение соответствует формату email send --email "user@example.com" diff --git a/docs/root/overriding_formatting.rst b/docs/root/overriding_formatting.rst index 5ba96c6..df7e4c3 100644 --- a/docs/root/overriding_formatting.rst +++ b/docs/root/overriding_formatting.rst @@ -41,3 +41,4 @@ .. literalinclude:: ../code_snippets/overriding_format_example_sample.py :language: python + :linenos: diff --git a/docs/root/quickstart.rst b/docs/root/quickstart.rst index 60dbf48..a2e881e 100644 --- a/docs/root/quickstart.rst +++ b/docs/root/quickstart.rst @@ -13,11 +13,13 @@ .. literalinclude:: ../code_snippets/quickstart_example_routers.py :language: python + :linenos: 3. **Определение приложения и оркестратора**. Для запуска приложения необходимо вызвать ``.include_router()`` у созданного приложения и передать ему раннее созданный роутер, после этого необходимо вызвать ``.start_polling()`` у созданного оркестратора и передать ему созданное приложение. .. literalinclude:: ../code_snippets/quickstart_example_main.py :language: python + :linenos: 4. **Запуск приложения**. Запускаем приложение как обычный скрипт. diff --git a/docs/root/redirect_stdout.rst b/docs/root/redirect_stdout.rst index 8bc380f..8637180 100644 --- a/docs/root/redirect_stdout.rst +++ b/docs/root/redirect_stdout.rst @@ -11,16 +11,19 @@ ----- Механизм перехвата ``stdout`` ------------------------------ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ По умолчанию ``Argenta`` перехватывает весь текст, который выводится в ``stdout`` внутри обработчика команды (``handler``). Это делается для реализации **динамической длины разделителя**. Система анализирует весь выведенный текст, находит самую длинную строку и использует её длину для отрисовки верхней и нижней разделительных линий. Это создает аккуратный и визуально согласованный интерфейс, где вывод команды "обернут" в рамку, идеально подогнанную под его содержимое. +----- + Побочные эффекты перехвата ``stdout`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Главный побочный эффект этого механизма проявляется при использовании функций, которые одновременно ожидают ввод от пользователя и выводят текст-приглашение. Классический пример — стандартная функция ``input()``. .. code-block:: python + :linenos: # Внутри обработчика команды user_name = input("Введите ваше имя: ") @@ -42,6 +45,7 @@ .. literalinclude:: ../code_snippets/redirect_stdout_example_sample.py :language: python + :linenos: В этом случае ``input()`` будет работать как обычно, и пользователь сразу увидит приглашение "Как вас зовут?". @@ -67,6 +71,7 @@ .. literalinclude:: ../code_snippets/redirect_stdout_example_sample2.py :language: python + :linenos: -----