mirror of
https://github.com/koloideal/Argenta.git
synced 2026-06-10 10:05:28 +03:00
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Argenta \n"
|
"Project-Id-Version: Argenta \n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-12-02 22:27+0300\n"
|
"POT-Creation-Date: 2026-01-22 04:26+0300\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language: en\n"
|
"Language: en\n"
|
||||||
@@ -29,92 +29,107 @@ msgid ""
|
|||||||
"подсказки и завершая ввод на основе истории команд, что ускоряет работу и"
|
"подсказки и завершая ввод на основе истории команд, что ускоряет работу и"
|
||||||
" снижает вероятность опечаток."
|
" снижает вероятность опечаток."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"``AutoCompleter`` is a component responsible for interactive command autocompletion. "
|
"``AutoCompleter`` is a component responsible for interactive command "
|
||||||
"It improves user experience by offering suggestions and completing input based on "
|
"autocompletion. It improves user experience by offering suggestions and "
|
||||||
"command history, which speeds up work and reduces the likelihood of typos."
|
"completing input based on command history, which speeds up work and "
|
||||||
|
"reduces the likelihood of typos."
|
||||||
|
|
||||||
#: ../../root/api/app/autocompleter.rst:11
|
#: ../../root/api/app/autocompleter.rst:11
|
||||||
msgid "Инициализация"
|
msgid "Инициализация"
|
||||||
msgstr "Initialization"
|
msgstr "Initialization"
|
||||||
|
|
||||||
#: ../../root/api/app/autocompleter.rst:18
|
#: ../../root/api/app/autocompleter.rst:21
|
||||||
msgid "Создаёт и настраивает экземпляр ``AutoCompleter``."
|
msgid "Создаёт и настраивает экземпляр ``AutoCompleter``."
|
||||||
msgstr "Creates and configures an ``AutoCompleter`` instance."
|
msgstr "Creates and configures an ``AutoCompleter`` instance."
|
||||||
|
|
||||||
#: ../../root/api/app/autocompleter.rst:20
|
#: ../../root/api/app/autocompleter.rst:23
|
||||||
msgid ""
|
msgid ""
|
||||||
"``history_filename``: Имя файла для сохранения истории команд. Если "
|
"``history_filename``: Имя файла для сохранения истории команд. Если "
|
||||||
"указано, история будет сохраняться между сессиями. При значении ``None`` "
|
"указано, история будет сохраняться между сессиями. При значении ``None`` "
|
||||||
"история хранится только в контексте сессии."
|
"история хранится только в контексте сессии."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"``history_filename``: Filename for saving command history. If specified, history "
|
"``history_filename``: Filename for saving command history. If specified, "
|
||||||
"will be saved between sessions. When set to ``None``, history is stored only within "
|
"history will be saved between sessions. When set to ``None``, history is "
|
||||||
"the session context."
|
"stored only within the session context."
|
||||||
|
|
||||||
#: ../../root/api/app/autocompleter.rst:21
|
#: ../../root/api/app/autocompleter.rst:24
|
||||||
msgid ""
|
msgid ""
|
||||||
"``autocomplete_button``: Клавиша, активирующая автодополнение. По "
|
"``autocomplete_button``: Клавиша, активирующая автодополнение. По "
|
||||||
"умолчанию — **Tab**."
|
"умолчанию — **Tab**."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"``autocomplete_button``: Key that activates autocompletion. Defaults to **Tab**."
|
"``autocomplete_button``: Key that activates autocompletion. Defaults to "
|
||||||
|
"**Tab**."
|
||||||
|
|
||||||
|
#: ../../root/api/app/autocompleter.rst:25
|
||||||
|
msgid ""
|
||||||
|
"``command_highlighting``: Если True, то в реальном времени при вводе "
|
||||||
|
"команды она будет подсвечиваться: зелёным, если такой триггер существует "
|
||||||
|
"и красный, если нет."
|
||||||
|
msgstr ""
|
||||||
|
"``command_highlighting``: If True, then in real time, when entering a "
|
||||||
|
" command, it will be highlighted: green if such a trigger exists "
|
||||||
|
"and red if not."
|
||||||
|
|
||||||
#: ../../root/api/app/autocompleter.rst:26
|
#: ../../root/api/app/autocompleter.rst:26
|
||||||
|
msgid ""
|
||||||
|
"``auto_suggestions``: Если True, то дополнение до раннее введённой "
|
||||||
|
"команды будет сразу отображаться светло-серым в строке ввода."
|
||||||
|
msgstr ""
|
||||||
|
"``auto_suggestions``: If True, the addition to the previously entered "
|
||||||
|
" command will immediately be displayed in light gray in the input line."
|
||||||
|
|
||||||
|
#: ../../root/api/app/autocompleter.rst:31
|
||||||
msgid "Назначение и возможности"
|
msgid "Назначение и возможности"
|
||||||
msgstr "Purpose and Features"
|
msgstr "Purpose and Features"
|
||||||
|
|
||||||
#: ../../root/api/app/autocompleter.rst:28
|
#: ../../root/api/app/autocompleter.rst:33
|
||||||
msgid "Основные возможности ``AutoCompleter``:"
|
msgid "Основные возможности ``AutoCompleter``:"
|
||||||
msgstr "Main features of ``AutoCompleter``:"
|
msgstr "Main features of ``AutoCompleter``:"
|
||||||
|
|
||||||
#: ../../root/api/app/autocompleter.rst:30
|
#: ../../root/api/app/autocompleter.rst:35
|
||||||
msgid ""
|
msgid ""
|
||||||
"**Автодополнение по истории**: При нажатии клавиши автодополнения (по "
|
"**Автодополнение по истории**: При нажатии клавиши автодополнения (по "
|
||||||
"умолчанию **Tab**) система ищет в истории команды, начинающиеся с уже "
|
"умолчанию **Tab**) система ищет в истории команды, начинающиеся с уже "
|
||||||
"введённого текста."
|
"введённого текста."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"**History-based autocompletion**: When the autocompletion key is pressed (by default **Tab**), "
|
"**History-based autocompletion**: When the autocompletion key is pressed "
|
||||||
"the system searches history for commands starting with the already entered text."
|
"(by default **Tab**), the system searches history for commands starting "
|
||||||
|
"with the already entered text."
|
||||||
|
|
||||||
#: ../../root/api/app/autocompleter.rst:32
|
#: ../../root/api/app/autocompleter.rst:37
|
||||||
msgid ""
|
msgid ""
|
||||||
"**Общий префикс**: Если найдено несколько команд с общим префиксом, будет"
|
"**Общий префикс**: Если найдено несколько команд с общим префиксом, будет"
|
||||||
" подставлена только общая часть. Например, для команд ``show_users`` и "
|
" подставлена только общая часть. Например, для команд ``show_users`` и "
|
||||||
"``show_profile`` при вводе ``sho`` и нажатии **Tab** ввод дополнится до "
|
"``show_profile`` при вводе ``sho`` и нажатии **Tab** ввод дополнится до "
|
||||||
"``show_``."
|
"``show_``."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"**Common prefix**: If multiple commands with a common prefix are found, only the common "
|
"**Common prefix**: If multiple commands with a common prefix are found, "
|
||||||
"part will be inserted. For example, for commands ``show_users`` and ``show_profile``, "
|
"only the common part will be inserted. For example, for commands "
|
||||||
"when entering ``sho`` and pressing **Tab**, the input will be completed to ``show_``."
|
"``show_users`` and ``show_profile``, when entering ``sho`` and pressing "
|
||||||
|
"**Tab**, the input will be completed to ``show_``."
|
||||||
|
|
||||||
#: ../../root/api/app/autocompleter.rst:34
|
#: ../../root/api/app/autocompleter.rst:39
|
||||||
msgid ""
|
msgid ""
|
||||||
"**Постоянная история**: Если указан ``history_filename``, история команд "
|
"**Постоянная история**: Если указан ``history_filename``, история команд "
|
||||||
"сохраняется в файл при выходе и загружается при следующем запуске. Это "
|
"сохраняется в файл при выходе и загружается при следующем запуске. Это "
|
||||||
"делает автодополнение со временем «умнее»."
|
"делает автодополнение со временем «умнее»."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"**Persistent history**: If ``history_filename`` is specified, command history is saved "
|
"**Persistent history**: If ``history_filename`` is specified, command "
|
||||||
"to a file on exit and loaded on the next startup. This makes autocompletion \"smarter\" over time."
|
"history is saved to a file on exit and loaded on the next startup. This "
|
||||||
|
"makes autocompletion \"smarter\" over time."
|
||||||
|
|
||||||
#: ../../root/api/app/autocompleter.rst:36
|
#: ../../root/api/app/autocompleter.rst:41
|
||||||
msgid ""
|
|
||||||
"**Очистка истории**: При сохранении ``AutoCompleter`` удаляет дубликаты и"
|
|
||||||
" несуществующие команды, поддерживая историю в актуальном состоянии."
|
|
||||||
msgstr ""
|
|
||||||
"**History cleanup**: When saving, ``AutoCompleter`` removes duplicates and non-existent "
|
|
||||||
"commands, keeping the history up to date."
|
|
||||||
|
|
||||||
#: ../../root/api/app/autocompleter.rst:38
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"**Настройка клавиши**: Клавишу автодополнения можно изменить с помощью "
|
"**Настройка клавиши**: Клавишу автодополнения можно изменить с помощью "
|
||||||
"параметра ``autocomplete_button``."
|
"параметра ``autocomplete_button``."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"**Key customization**: The autocompletion key can be changed using the ``autocomplete_button`` parameter."
|
"**Key customization**: The autocompletion key can be changed using the "
|
||||||
|
"``autocomplete_button`` parameter."
|
||||||
|
|
||||||
#: ../../root/api/app/autocompleter.rst:43
|
#: ../../root/api/app/autocompleter.rst:46
|
||||||
msgid "Пример использования"
|
msgid "Пример использования"
|
||||||
msgstr "Usage Example"
|
msgstr "Usage Example"
|
||||||
|
|
||||||
#: ../../root/api/app/autocompleter.rst:45
|
#: ../../root/api/app/autocompleter.rst:48
|
||||||
msgid "``AutoCompleter`` передаётся как аргумент при инициализации `App`."
|
msgid "``AutoCompleter`` передаётся как аргумент при инициализации `App`."
|
||||||
msgstr "``AutoCompleter`` is passed as an argument when initializing `App`."
|
msgstr "``AutoCompleter`` is passed as an argument when initializing `App`."
|
||||||
|
|
||||||
|
|||||||
@@ -12,13 +12,18 @@ AutoCompleter
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
__init__(self, history_filename: str | None = None,
|
__init__(self,
|
||||||
autocomplete_button: str = "tab") -> None
|
history_filename: str | None = None,
|
||||||
|
autocomplete_button: str = "tab",
|
||||||
|
command_highlighting: bool = True,
|
||||||
|
auto_suggestions: bool = True) -> None:
|
||||||
|
|
||||||
Создаёт и настраивает экземпляр ``AutoCompleter``.
|
Создаёт и настраивает экземпляр ``AutoCompleter``.
|
||||||
|
|
||||||
* ``history_filename``: Имя файла для сохранения истории команд. Если указано, история будет сохраняться между сессиями. При значении ``None`` история хранится только в контексте сессии.
|
* ``history_filename``: Имя файла для сохранения истории команд. Если указано, история будет сохраняться между сессиями. При значении ``None`` история хранится только в контексте сессии.
|
||||||
* ``autocomplete_button``: Клавиша, активирующая автодополнение. По умолчанию — **Tab**.
|
* ``autocomplete_button``: Клавиша, активирующая автодополнение. По умолчанию — **Tab**.
|
||||||
|
* ``command_highlighting``: Если True, то в реальном времени при вводе команды она будет подсвечиваться: зелёным, если такой триггер существует и красный, если нет.
|
||||||
|
* ``auto_suggestions``: Если True, то дополнение до раннее введённой команды будет сразу отображаться светло-серым в строке ввода.
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
|
||||||
@@ -33,8 +38,6 @@ AutoCompleter
|
|||||||
|
|
||||||
* **Постоянная история**: Если указан ``history_filename``, история команд сохраняется в файл при выходе и загружается при следующем запуске. Это делает автодополнение со временем «умнее».
|
* **Постоянная история**: Если указан ``history_filename``, история команд сохраняется в файл при выходе и загружается при следующем запуске. Это делает автодополнение со временем «умнее».
|
||||||
|
|
||||||
* **Очистка истории**: При сохранении ``AutoCompleter`` удаляет дубликаты и несуществующие команды, поддерживая историю в актуальном состоянии.
|
|
||||||
|
|
||||||
* **Настройка клавиши**: Клавишу автодополнения можно изменить с помощью параметра ``autocomplete_button``.
|
* **Настройка клавиши**: Клавишу автодополнения можно изменить с помощью параметра ``autocomplete_button``.
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
|||||||
+10
-57
@@ -1,61 +1,14 @@
|
|||||||
import math
|
from argenta.app import AutoCompleter
|
||||||
|
|
||||||
|
|
||||||
def estimate_nth_prime_upper_bound(n: int):
|
if __name__ == "__main__":
|
||||||
if n < 6:
|
test_commands: set[str] = {"start", "qwertyu", "stop", "exit"}
|
||||||
return 15
|
hist_file: str = "history.txt"
|
||||||
|
|
||||||
log_n = math.log(n)
|
ac: AutoCompleter = AutoCompleter(autocomplete_button='tab')
|
||||||
log_log_n = math.log(log_n)
|
ac.initial_setup(test_commands)
|
||||||
|
|
||||||
if n < 100:
|
while True:
|
||||||
return int(n * (log_n + log_log_n) * 1.5)
|
inp: str = ac.prompt(">>> ").strip()
|
||||||
elif n < 1000:
|
if inp == "exit":
|
||||||
return int(n * (log_n + log_log_n) * 1.3)
|
break
|
||||||
elif n >= 8009824:
|
|
||||||
return int(n * (log_n + log_log_n - 1 + 1.8 * log_log_n / log_n))
|
|
||||||
else:
|
|
||||||
return int(n * (log_n + log_log_n - 1 + 2.0 * log_log_n / log_n))
|
|
||||||
|
|
||||||
|
|
||||||
def odd_dig_primes(n: int) -> list[int]:
|
|
||||||
nums = {k: True for k in range(2, n+1)}
|
|
||||||
|
|
||||||
for num, is_checkable in nums.items():
|
|
||||||
if not is_checkable:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if nums[2]:
|
|
||||||
nums[2] = False
|
|
||||||
|
|
||||||
for x in range(num * num, n, num):
|
|
||||||
nums[x] = False
|
|
||||||
|
|
||||||
primes = len([x for x in nums.items() if x[1]])
|
|
||||||
max_prime = max([x[0] for x in nums.items() if x[1]])
|
|
||||||
|
|
||||||
upper_bound = estimate_nth_prime_upper_bound(primes+1)
|
|
||||||
print(upper_bound)
|
|
||||||
nums2 = {k: True for k in range(2, upper_bound)}
|
|
||||||
|
|
||||||
for num, is_checkable in nums2.items():
|
|
||||||
if not is_checkable:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if nums2[2]:
|
|
||||||
nums2[2] = False
|
|
||||||
|
|
||||||
for x in range(num * num, upper_bound, num):
|
|
||||||
nums2[x] = False
|
|
||||||
|
|
||||||
print([x for x in nums2.items() if x[1]])
|
|
||||||
|
|
||||||
next_prime_after_max = [x[0] for x in nums2.items() if x[1]][-1]
|
|
||||||
|
|
||||||
return [
|
|
||||||
primes,
|
|
||||||
max_prime,
|
|
||||||
next_prime_after_max
|
|
||||||
]
|
|
||||||
|
|
||||||
print(odd_dig_primes(13))
|
|
||||||
|
|||||||
+1
-1
@@ -9,8 +9,8 @@ license = { text = "MIT" }
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"rich (>=14.0.0,<15.0.0)",
|
"rich (>=14.0.0,<15.0.0)",
|
||||||
"art (>=6.4,<7.0)",
|
"art (>=6.4,<7.0)",
|
||||||
"pyreadline3>=3.5.4; sys_platform == 'win32'",
|
|
||||||
"dishka>=1.7.2",
|
"dishka>=1.7.2",
|
||||||
|
"prompt-toolkit>=3.0.52",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
|
|||||||
@@ -1,97 +1,138 @@
|
|||||||
__all__ = ["AutoCompleter"]
|
__all__ = ["AutoCompleter"]
|
||||||
|
|
||||||
import os
|
import sys
|
||||||
import readline
|
from typing import Callable, Iterable
|
||||||
from typing import Never
|
|
||||||
|
from prompt_toolkit import PromptSession, HTML
|
||||||
|
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
||||||
|
from prompt_toolkit.completion import Completer, Completion, CompleteEvent
|
||||||
|
from prompt_toolkit.document import Document
|
||||||
|
from prompt_toolkit.formatted_text import StyleAndTextTuples
|
||||||
|
from prompt_toolkit.history import History, ThreadedHistory, FileHistory, InMemoryHistory
|
||||||
|
from prompt_toolkit.key_binding import KeyBindings, KeyPressEvent
|
||||||
|
from prompt_toolkit.lexers import Lexer
|
||||||
|
from prompt_toolkit.styles import Style
|
||||||
|
|
||||||
|
|
||||||
|
class CommandLexer(Lexer):
|
||||||
|
def __init__(self, valid_commands: set[str]) -> None:
|
||||||
|
self.valid_commands: set[str] = valid_commands
|
||||||
|
|
||||||
|
def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]:
|
||||||
|
def get_line_tokens(lineno: int) -> StyleAndTextTuples:
|
||||||
|
if lineno >= len(document.lines):
|
||||||
|
return []
|
||||||
|
|
||||||
|
line_text: str = document.lines[lineno]
|
||||||
|
|
||||||
|
if not line_text.strip():
|
||||||
|
return [("", line_text)]
|
||||||
|
|
||||||
|
first_word: str = line_text.split()[0] if line_text.split() else ""
|
||||||
|
|
||||||
|
if first_word in self.valid_commands:
|
||||||
|
return [("class:valid", line_text)]
|
||||||
|
else:
|
||||||
|
return [("class:invalid", line_text)]
|
||||||
|
|
||||||
|
return get_line_tokens
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryCompleter(Completer):
|
||||||
|
def __init__(self, history_container: History, static_commands: set[str]) -> None:
|
||||||
|
self.history_container: History = history_container
|
||||||
|
self.static_commands: set[str] = static_commands
|
||||||
|
|
||||||
|
def get_completions(self, document: Document, complete_event: CompleteEvent) -> Iterable[Completion]:
|
||||||
|
text: str = document.text_before_cursor
|
||||||
|
history_items: set[str] = set(self.history_container.load_history_strings())
|
||||||
|
all_candidates: set[str] = history_items.union(self.static_commands)
|
||||||
|
matches: list[str] = sorted(cmd for cmd in all_candidates if cmd.startswith(text))
|
||||||
|
|
||||||
|
if not matches:
|
||||||
|
return
|
||||||
|
|
||||||
|
for match in matches:
|
||||||
|
yield Completion(
|
||||||
|
match,
|
||||||
|
start_position=-len(text),
|
||||||
|
display=match
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _find_common_prefix(matches: list[str]) -> str:
|
||||||
|
if not matches:
|
||||||
|
return ""
|
||||||
|
common: str = matches[0]
|
||||||
|
for match in matches[1:]:
|
||||||
|
i: int = 0
|
||||||
|
while i < len(common) and i < len(match) and common[i] == match[i]:
|
||||||
|
i += 1
|
||||||
|
common = common[:i]
|
||||||
|
return common
|
||||||
|
|
||||||
|
|
||||||
class AutoCompleter:
|
class AutoCompleter:
|
||||||
def __init__(
|
def __init__(
|
||||||
self, history_filename: str | None = None, autocomplete_button: str = "tab"
|
self,
|
||||||
|
history_filename: str | None = None,
|
||||||
|
autocomplete_button: str = "tab",
|
||||||
|
command_highlighting: bool = True,
|
||||||
|
auto_suggestions: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
|
||||||
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 autocomplete_button: the button for auto-completion
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self.history_filename: str | None = history_filename
|
self.history_filename: str | None = history_filename
|
||||||
self.autocomplete_button: str = autocomplete_button
|
self.autocomplete_button: str = autocomplete_button
|
||||||
|
self.command_highlighting: bool = command_highlighting
|
||||||
def _complete(self, text: str, state: int) -> str | None:
|
self.auto_suggestions: bool = auto_suggestions
|
||||||
"""
|
self._session: PromptSession[str] | None = None
|
||||||
Private. Auto-completion function
|
self._fallback_mode: bool = False
|
||||||
:param text: part of the command being entered
|
|
||||||
:param state: the current cursor position is relative to the beginning of the line
|
|
||||||
:return: the desired candidate as str or None
|
|
||||||
"""
|
|
||||||
matches: list[str] = sorted(
|
|
||||||
cmd for cmd in _get_history_items() if cmd.startswith(text)
|
|
||||||
)
|
|
||||||
if len(matches) > 1:
|
|
||||||
common_prefix = matches[0]
|
|
||||||
for match in matches[1:]:
|
|
||||||
i = 0
|
|
||||||
while (
|
|
||||||
i < len(common_prefix)
|
|
||||||
and i < len(match)
|
|
||||||
and common_prefix[i] == match[i]
|
|
||||||
):
|
|
||||||
i += 1
|
|
||||||
common_prefix = common_prefix[:i]
|
|
||||||
if state == 0:
|
|
||||||
readline.insert_text(common_prefix[len(text) :])
|
|
||||||
readline.redisplay()
|
|
||||||
return None
|
|
||||||
elif len(matches) == 1:
|
|
||||||
return matches[0] if state == 0 else None
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def initial_setup(self, all_commands: set[str]) -> None:
|
def initial_setup(self, all_commands: set[str]) -> None:
|
||||||
"""
|
if not sys.stdin.isatty():
|
||||||
Private. Initial setup function
|
self._session = None
|
||||||
:param all_commands: Registered commands for adding them to the autocomplete history
|
self._fallback_mode = True
|
||||||
:return: None
|
return
|
||||||
"""
|
|
||||||
if self.history_filename:
|
kb = KeyBindings()
|
||||||
if os.path.exists(self.history_filename):
|
|
||||||
readline.read_history_file(self.history_filename)
|
def _(event: KeyPressEvent) -> None:
|
||||||
|
buff = event.app.current_buffer
|
||||||
|
|
||||||
|
if buff.complete_state:
|
||||||
|
buff.complete_next()
|
||||||
else:
|
else:
|
||||||
for line in all_commands:
|
completions = list(buff.completer.get_completions(buff.document, CompleteEvent()))
|
||||||
readline.add_history(line)
|
if len(completions) == 1:
|
||||||
|
buff.apply_completion(completions[0])
|
||||||
|
else:
|
||||||
|
buff.start_completion(select_first=False)
|
||||||
|
|
||||||
if not self.history_filename:
|
kb.add(self.autocomplete_button)(_)
|
||||||
for line in all_commands:
|
|
||||||
readline.add_history(line)
|
|
||||||
|
|
||||||
readline.set_completer(self._complete)
|
history: InMemoryHistory | ThreadedHistory
|
||||||
readline.set_completer_delims(readline.get_completer_delims().replace(" ", ""))
|
|
||||||
readline.parse_and_bind(f"{self.autocomplete_button}: complete")
|
|
||||||
|
|
||||||
def exit_setup(self, all_commands: set[str]) -> None:
|
|
||||||
"""
|
|
||||||
Private. Exit setup function
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
if self.history_filename:
|
if self.history_filename:
|
||||||
readline.write_history_file(self.history_filename)
|
history = ThreadedHistory(FileHistory(self.history_filename))
|
||||||
with open(self.history_filename, "r") as history_file:
|
else:
|
||||||
raw_history = history_file.read()
|
history = InMemoryHistory()
|
||||||
pretty_history: list[str] = []
|
|
||||||
for line in set(raw_history.strip().split("\n")):
|
|
||||||
if line.split()[0] in all_commands:
|
|
||||||
pretty_history.append(line)
|
|
||||||
with open(self.history_filename, "w") as history_file:
|
|
||||||
_ = history_file.write("\n".join(pretty_history))
|
|
||||||
|
|
||||||
|
style = Style.from_dict({'valid': '#00ff00', 'invalid': '#ff0000'})
|
||||||
|
|
||||||
def _get_history_items() -> list[str] | list[Never]:
|
self._session = PromptSession(
|
||||||
"""
|
history=history,
|
||||||
Private. Returns a list of all commands entered by the user
|
completer=HistoryCompleter(history, all_commands),
|
||||||
:return: all commands entered by the user as list[str] | list[Never]
|
complete_while_typing=False,
|
||||||
"""
|
key_bindings=kb,
|
||||||
return [
|
auto_suggest=AutoSuggestFromHistory() if self.auto_suggestions else None,
|
||||||
readline.get_history_item(i)
|
style=style if self.command_highlighting else style,
|
||||||
for i in range(1, readline.get_current_history_length() + 1)
|
lexer=CommandLexer(all_commands) if self.command_highlighting else None,
|
||||||
]
|
)
|
||||||
|
|
||||||
|
def prompt(self, prompt_text: str | HTML = ">>> ") -> str:
|
||||||
|
if self._fallback_mode:
|
||||||
|
return input(prompt_text if isinstance(prompt_text, str) else ">>> ")
|
||||||
|
if self._session is None:
|
||||||
|
raise RuntimeError("Call initial_setup() before using prompt()")
|
||||||
|
return self._session.prompt(
|
||||||
|
HTML(prompt_text) if isinstance(prompt_text, str) else prompt_text
|
||||||
|
)
|
||||||
@@ -6,6 +6,7 @@ from contextlib import redirect_stdout
|
|||||||
from typing import Callable, Never, TypeAlias
|
from typing import Callable, Never, TypeAlias
|
||||||
|
|
||||||
from art import text2art
|
from art import text2art
|
||||||
|
from prompt_toolkit import HTML
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.markup import escape
|
from rich.markup import escape
|
||||||
|
|
||||||
@@ -37,7 +38,7 @@ class BaseApp:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
prompt: str,
|
prompt: str | HTML,
|
||||||
initial_message: str,
|
initial_message: str,
|
||||||
farewell_message: str,
|
farewell_message: str,
|
||||||
exit_command: Command,
|
exit_command: Command,
|
||||||
@@ -48,7 +49,7 @@ class BaseApp:
|
|||||||
autocompleter: AutoCompleter,
|
autocompleter: AutoCompleter,
|
||||||
print_func: Printer,
|
print_func: Printer,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._prompt: str = prompt
|
self._prompt: str | HTML = prompt
|
||||||
self._print_func: Printer = print_func
|
self._print_func: Printer = print_func
|
||||||
self._exit_command: Command = exit_command
|
self._exit_command: Command = exit_command
|
||||||
self._dividing_line: StaticDividingLine | DynamicDividingLine = dividing_line
|
self._dividing_line: StaticDividingLine | DynamicDividingLine = dividing_line
|
||||||
@@ -306,7 +307,7 @@ class BaseApp:
|
|||||||
Private. Sets up default app view
|
Private. Sets up default app view
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
self._prompt = f"[italic dim bold]{self._prompt}"
|
self._prompt = f"<gray><b>{self._prompt}</b></gray>"
|
||||||
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"
|
||||||
)
|
)
|
||||||
@@ -403,7 +404,7 @@ class App(BaseApp):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
prompt: str = "What do you want to do?\n\n",
|
prompt: str | HTML = ">>> ",
|
||||||
initial_message: str = "Argenta\n",
|
initial_message: str = "Argenta\n",
|
||||||
farewell_message: str = "\nSee you\n",
|
farewell_message: str = "\nSee you\n",
|
||||||
exit_command: Command = DEFAULT_EXIT_COMMAND,
|
exit_command: Command = DEFAULT_EXIT_COMMAND,
|
||||||
@@ -454,7 +455,7 @@ class App(BaseApp):
|
|||||||
if self._repeat_command_groups_printing:
|
if self._repeat_command_groups_printing:
|
||||||
self._print_command_group_description()
|
self._print_command_group_description()
|
||||||
|
|
||||||
raw_command: str = Console().input(self._prompt)
|
raw_command: str = self._autocompleter.prompt(self._prompt)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
input_command: InputCommand = InputCommand.parse(raw_command=raw_command)
|
input_command: InputCommand = InputCommand.parse(raw_command=raw_command)
|
||||||
@@ -467,7 +468,6 @@ class App(BaseApp):
|
|||||||
|
|
||||||
if self._is_exit_command(input_command):
|
if self._is_exit_command(input_command):
|
||||||
self.system_router.finds_appropriate_handler(input_command)
|
self.system_router.finds_appropriate_handler(input_command)
|
||||||
self._autocompleter.exit_setup(self.registered_routers.get_triggers())
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if self._is_unknown_command(input_command):
|
if self._is_unknown_command(input_command):
|
||||||
|
|||||||
@@ -361,7 +361,7 @@ def test_set_exit_command_handler_stores_handler() -> None:
|
|||||||
|
|
||||||
def test_setup_default_view_formats_prompt() -> None:
|
def test_setup_default_view_formats_prompt() -> None:
|
||||||
app = App(prompt='>>')
|
app = App(prompt='>>')
|
||||||
assert app._prompt == '[italic dim bold]>>'
|
assert app._prompt == '<gray><b>>></b></gray>'
|
||||||
|
|
||||||
|
|
||||||
def test_setup_default_view_sets_default_unknown_command_handler() -> None:
|
def test_setup_default_view_sets_default_unknown_command_handler() -> None:
|
||||||
|
|||||||
@@ -1,192 +1,162 @@
|
|||||||
import os
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pyfakefs.fake_filesystem import FakeFilesystem
|
from prompt_toolkit.document import Document
|
||||||
from pytest_mock import MockerFixture
|
from prompt_toolkit.history import InMemoryHistory
|
||||||
|
|
||||||
from argenta.app.autocompleter.entity import (
|
from argenta.app.autocompleter.entity import (
|
||||||
AutoCompleter,
|
AutoCompleter,
|
||||||
_get_history_items
|
CommandLexer,
|
||||||
|
HistoryCompleter
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
HISTORY_FILE: str = "test_history.txt"
|
|
||||||
COMMANDS: set[str] = {"start", "stop", "status"}
|
COMMANDS: set[str] = {"start", "stop", "status"}
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
def test_autocompleter_initializes_with_default_params() -> None:
|
||||||
# Fixtures
|
completer = AutoCompleter()
|
||||||
# ============================================================================
|
assert completer.history_filename is None
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def mock_readline(mocker: MockerFixture) -> Any:
|
|
||||||
_history: list[str] = []
|
|
||||||
|
|
||||||
def add_history(item: str) -> None:
|
|
||||||
_history.append(item)
|
|
||||||
|
|
||||||
def get_history_item(index: int) -> str | None:
|
|
||||||
if 1 <= index <= len(_history):
|
|
||||||
return _history[index - 1]
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_current_history_length() -> int:
|
|
||||||
return len(_history)
|
|
||||||
|
|
||||||
def clear_history() -> None:
|
|
||||||
_history.clear()
|
|
||||||
|
|
||||||
mock: Any = mocker.MagicMock() # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
||||||
mocker.patch('argenta.app.autocompleter.entity.readline', mock) # pyright: ignore[reportUnknownArgumentType]
|
|
||||||
|
|
||||||
mock.reset_mock() # pyright: ignore[reportUnknownMemberType]
|
|
||||||
clear_history()
|
|
||||||
|
|
||||||
mock.add_history.side_effect = add_history # pyright: ignore[reportUnknownMemberType]
|
|
||||||
mock.get_history_item.side_effect = get_history_item # pyright: ignore[reportUnknownMemberType]
|
|
||||||
mock.get_current_history_length.side_effect = get_current_history_length # pyright: ignore[reportUnknownMemberType]
|
|
||||||
mock.get_completer_delims.return_value = " " # pyright: ignore[reportUnknownMemberType]
|
|
||||||
|
|
||||||
return mock # pyright: ignore[reportReturnType, reportUnknownVariableType]
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# Tests for AutoCompleter initialization
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
|
|
||||||
def test_autocompleter_initializes_with_history_file_and_button() -> None:
|
|
||||||
completer: AutoCompleter = AutoCompleter(history_filename=HISTORY_FILE, autocomplete_button="tab")
|
|
||||||
assert completer.history_filename == HISTORY_FILE
|
|
||||||
assert completer.autocomplete_button == "tab"
|
assert completer.autocomplete_button == "tab"
|
||||||
|
assert completer.command_highlighting is True
|
||||||
|
assert completer.auto_suggestions is True
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
def test_autocompleter_initializes_with_custom_params() -> None:
|
||||||
# Tests for initial setup
|
completer = AutoCompleter(
|
||||||
# ============================================================================
|
history_filename="test.txt",
|
||||||
|
autocomplete_button="c-space",
|
||||||
|
command_highlighting=False,
|
||||||
|
auto_suggestions=False
|
||||||
|
)
|
||||||
|
assert completer.history_filename == "test.txt"
|
||||||
|
assert completer.autocomplete_button == "c-space"
|
||||||
|
assert completer.command_highlighting is False
|
||||||
|
assert completer.auto_suggestions is False
|
||||||
|
|
||||||
|
|
||||||
def test_initial_setup_creates_history_when_file_does_not_exist(fs: FakeFilesystem, mock_readline: Any) -> None:
|
def test_prompt_raises_error_without_initial_setup() -> None:
|
||||||
if os.path.exists(HISTORY_FILE):
|
completer = AutoCompleter()
|
||||||
os.remove(HISTORY_FILE)
|
with pytest.raises(RuntimeError, match="Call initial_setup"):
|
||||||
|
completer.prompt(">>> ")
|
||||||
completer: AutoCompleter = AutoCompleter(history_filename=HISTORY_FILE)
|
|
||||||
completer.initial_setup(COMMANDS)
|
|
||||||
|
|
||||||
mock_readline.read_history_file.assert_not_called()
|
|
||||||
assert mock_readline.add_history.call_count == len(COMMANDS)
|
|
||||||
|
|
||||||
mock_readline.set_completer.assert_called_with(completer._complete)
|
|
||||||
mock_readline.parse_and_bind.assert_called_with("tab: complete")
|
|
||||||
|
|
||||||
|
|
||||||
def test_initial_setup_reads_existing_history_file(fs: FakeFilesystem, mock_readline: Any) -> None:
|
def test_command_lexer_highlights_valid_command() -> None:
|
||||||
fs.create_file(HISTORY_FILE, contents="previous_command\n") # pyright: ignore[reportUnknownMemberType]
|
lexer = CommandLexer({"start", "stop"})
|
||||||
|
doc = Document("start server")
|
||||||
completer: AutoCompleter = AutoCompleter(history_filename=HISTORY_FILE)
|
tokens = lexer.lex_document(doc)(0)
|
||||||
completer.initial_setup(COMMANDS)
|
assert tokens == [("class:valid", "start server")]
|
||||||
|
|
||||||
mock_readline.read_history_file.assert_called_once_with(HISTORY_FILE)
|
|
||||||
mock_readline.add_history.assert_not_called()
|
|
||||||
mock_readline.set_completer.assert_called_once()
|
|
||||||
mock_readline.parse_and_bind.assert_called_once()
|
|
||||||
|
|
||||||
|
|
||||||
def test_initial_setup_works_without_history_filename(mock_readline: Any) -> None:
|
def test_command_lexer_highlights_invalid_command() -> None:
|
||||||
completer: AutoCompleter = AutoCompleter(history_filename=None)
|
lexer = CommandLexer({"start", "stop"})
|
||||||
completer.initial_setup(COMMANDS)
|
doc = Document("invalid command")
|
||||||
|
tokens = lexer.lex_document(doc)(0)
|
||||||
mock_readline.read_history_file.assert_not_called()
|
assert tokens == [("class:invalid", "invalid command")]
|
||||||
assert mock_readline.add_history.call_count == len(COMMANDS)
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
def test_command_lexer_handles_empty_line() -> None:
|
||||||
# Tests for exit setup and history filtering
|
lexer = CommandLexer({"start", "stop"})
|
||||||
# ============================================================================
|
doc = Document("")
|
||||||
|
tokens = lexer.lex_document(doc)(0)
|
||||||
|
assert tokens == [("", "")]
|
||||||
|
|
||||||
|
|
||||||
def test_exit_setup_writes_and_filters_duplicate_commands(fs: FakeFilesystem, mock_readline: Any) -> None:
|
def test_command_lexer_handles_whitespace_only() -> None:
|
||||||
mock_readline.add_history.side_effect = None
|
lexer = CommandLexer({"start", "stop"})
|
||||||
mock_readline.add_history("start server")
|
doc = Document(" ")
|
||||||
mock_readline.add_history("stop client")
|
tokens = lexer.lex_document(doc)(0)
|
||||||
mock_readline.add_history("invalid command")
|
assert tokens == [("", " ")]
|
||||||
mock_readline.add_history("start server")
|
|
||||||
|
|
||||||
raw_history_content: str = "\n".join(["start server", "stop client", "invalid command", "start server"])
|
|
||||||
fs.create_file(HISTORY_FILE, contents=raw_history_content) # pyright: ignore[reportUnknownMemberType]
|
|
||||||
|
|
||||||
completer: AutoCompleter = AutoCompleter(history_filename=HISTORY_FILE)
|
|
||||||
completer.exit_setup(all_commands={"start", "stop"})
|
|
||||||
|
|
||||||
mock_readline.write_history_file.assert_called_once_with(HISTORY_FILE)
|
|
||||||
|
|
||||||
with open(HISTORY_FILE) as f:
|
|
||||||
content: str = f.read()
|
|
||||||
lines: list[str] = sorted(content.strip().split("\n"))
|
|
||||||
assert lines == ["start server", "stop client"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_exit_setup_skips_writing_when_no_history_filename(mock_readline: Any) -> None:
|
def test_history_completer_returns_matching_commands() -> None:
|
||||||
completer: AutoCompleter = AutoCompleter(history_filename=None)
|
history = InMemoryHistory()
|
||||||
completer.exit_setup(all_commands=COMMANDS)
|
history.append_string("start server")
|
||||||
mock_readline.write_history_file.assert_not_called()
|
history.append_string("stop server")
|
||||||
|
|
||||||
|
completer = HistoryCompleter(history, {"status"})
|
||||||
|
doc = Document("sta")
|
||||||
|
|
||||||
|
completions = list(completer.get_completions(doc, None))
|
||||||
|
completion_texts = [c.text for c in completions]
|
||||||
|
|
||||||
|
assert "start server" in completion_texts
|
||||||
|
assert "status" in completion_texts
|
||||||
|
assert "stop server" not in completion_texts
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
def test_history_completer_returns_all_when_empty_input() -> None:
|
||||||
# Tests for autocomplete functionality
|
history = InMemoryHistory()
|
||||||
# ============================================================================
|
history.append_string("start")
|
||||||
|
history.append_string("stop")
|
||||||
|
|
||||||
|
completer = HistoryCompleter(history, {"status"})
|
||||||
|
doc = Document("")
|
||||||
|
|
||||||
|
completions = list(completer.get_completions(doc, None))
|
||||||
|
completion_texts = [c.text for c in completions]
|
||||||
|
|
||||||
|
assert len(completion_texts) == 3
|
||||||
|
assert "start" in completion_texts
|
||||||
|
assert "stop" in completion_texts
|
||||||
|
assert "status" in completion_texts
|
||||||
|
|
||||||
|
|
||||||
def test_complete_returns_none_when_no_matches_found(mock_readline: Any) -> None:
|
def test_history_completer_returns_empty_when_no_matches() -> None:
|
||||||
cmd: str
|
history = InMemoryHistory()
|
||||||
for cmd in ["start", "stop"]:
|
history.append_string("start")
|
||||||
mock_readline.add_history(cmd)
|
|
||||||
|
|
||||||
completer: AutoCompleter = AutoCompleter()
|
completer = HistoryCompleter(history, {"stop"})
|
||||||
assert completer._complete("run", 0) is None
|
doc = Document("xyz")
|
||||||
assert completer._complete("run", 1) is None
|
|
||||||
|
completions = list(completer.get_completions(doc, None))
|
||||||
|
assert len(completions) == 0
|
||||||
|
|
||||||
|
|
||||||
def test_complete_returns_single_match(mock_readline: Any) -> None:
|
def test_history_completer_deduplicates_commands() -> None:
|
||||||
mock_readline.add_history("start server")
|
history = InMemoryHistory()
|
||||||
mock_readline.add_history("stop server")
|
history.append_string("start")
|
||||||
|
history.append_string("start")
|
||||||
|
|
||||||
completer: AutoCompleter = AutoCompleter()
|
completer = HistoryCompleter(history, {"start"})
|
||||||
assert completer._complete("start", 0) == "start server"
|
doc = Document("sta")
|
||||||
assert completer._complete("start", 1) is None
|
|
||||||
|
completions = list(completer.get_completions(doc, None))
|
||||||
|
assert len(completions) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_complete_inserts_common_prefix_for_multiple_matches(mock_readline: Any) -> None:
|
def test_history_completer_sorts_results() -> None:
|
||||||
mock_readline.add_history("status client")
|
history = InMemoryHistory()
|
||||||
mock_readline.add_history("status server")
|
history.append_string("stop")
|
||||||
mock_readline.add_history("stop")
|
history.append_string("start")
|
||||||
|
history.append_string("status")
|
||||||
|
|
||||||
completer: AutoCompleter = AutoCompleter()
|
completer = HistoryCompleter(history, set())
|
||||||
|
doc = Document("st")
|
||||||
|
|
||||||
result: str | None = completer._complete("stat", 0)
|
completions = list(completer.get_completions(doc, None))
|
||||||
assert result is None
|
completion_texts = [c.text for c in completions]
|
||||||
mock_readline.insert_text.assert_called_once_with("us ")
|
|
||||||
mock_readline.redisplay.assert_called_once()
|
|
||||||
|
|
||||||
mock_readline.reset_mock()
|
assert completion_texts == ["start", "status", "stop"]
|
||||||
result_state_1: str | None = completer._complete("stat", 1)
|
|
||||||
assert result_state_1 is None
|
|
||||||
mock_readline.insert_text.assert_not_called()
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
def test_find_common_prefix_with_multiple_matches() -> None:
|
||||||
# Tests for helper functions
|
matches = ["start server", "start client", "start process"]
|
||||||
# ============================================================================
|
prefix = HistoryCompleter._find_common_prefix(matches)
|
||||||
|
assert prefix == "start "
|
||||||
|
|
||||||
|
|
||||||
def test_get_history_items_returns_empty_list_initially(mock_readline: Any) -> None:
|
def test_find_common_prefix_with_no_common() -> None:
|
||||||
assert _get_history_items() == []
|
matches = ["start", "stop", "status"]
|
||||||
|
prefix = HistoryCompleter._find_common_prefix(matches)
|
||||||
|
assert prefix == "st"
|
||||||
|
|
||||||
|
|
||||||
def test_get_history_items_returns_all_added_items(mock_readline: Any) -> None:
|
def test_find_common_prefix_with_single_match() -> None:
|
||||||
mock_readline.add_history("first item")
|
matches = ["start"]
|
||||||
mock_readline.add_history("second item")
|
prefix = HistoryCompleter._find_common_prefix(matches)
|
||||||
|
assert prefix == "start"
|
||||||
|
|
||||||
assert _get_history_items() == ["first item", "second item"]
|
|
||||||
|
def test_find_common_prefix_with_empty_list() -> None:
|
||||||
|
matches: list[str] = []
|
||||||
|
prefix = HistoryCompleter._find_common_prefix(matches)
|
||||||
|
assert prefix == ""
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ source = { editable = "." }
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "art" },
|
{ name = "art" },
|
||||||
{ name = "dishka" },
|
{ name = "dishka" },
|
||||||
{ name = "pyreadline3", marker = "sys_platform == 'win32'" },
|
{ name = "prompt-toolkit" },
|
||||||
{ name = "rich" },
|
{ name = "rich" },
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ typecheckers = [
|
|||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "art", specifier = ">=6.4,<7.0" },
|
{ name = "art", specifier = ">=6.4,<7.0" },
|
||||||
{ name = "dishka", specifier = ">=1.7.2" },
|
{ name = "dishka", specifier = ">=1.7.2" },
|
||||||
{ name = "pyreadline3", marker = "sys_platform == 'win32'", specifier = ">=3.5.4" },
|
{ name = "prompt-toolkit", specifier = ">=3.0.52" },
|
||||||
{ name = "rich", specifier = ">=14.0.0,<15.0.0" },
|
{ name = "rich", specifier = ">=14.0.0,<15.0.0" },
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -589,6 +589,18 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prompt-toolkit"
|
||||||
|
version = "3.0.52"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "wcwidth" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pycodestyle"
|
name = "pycodestyle"
|
||||||
version = "2.14.0"
|
version = "2.14.0"
|
||||||
@@ -651,15 +663,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/e6/3d/8888e7ca0c6b093b52aa5c6693b0022e66d5958adcc685ed7a6a8ae615e8/pygments_styles-0.2.0-py3-none-any.whl", hash = "sha256:40fb7f1d34ce2b2792aecabc8d3877ca364eb04bb3b7f7747cfc9a7f0569bae9", size = 34200, upload-time = "2025-09-26T08:39:02.262Z" },
|
{ url = "https://files.pythonhosted.org/packages/e6/3d/8888e7ca0c6b093b52aa5c6693b0022e66d5958adcc685ed7a6a8ae615e8/pygments_styles-0.2.0-py3-none-any.whl", hash = "sha256:40fb7f1d34ce2b2792aecabc8d3877ca364eb04bb3b7f7747cfc9a7f0569bae9", size = 34200, upload-time = "2025-09-26T08:39:02.262Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pyreadline3"
|
|
||||||
version = "3.5.4"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839, upload-time = "2024-09-19T02:40:10.062Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178, upload-time = "2024-09-19T02:40:08.598Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "8.4.2"
|
version = "8.4.2"
|
||||||
@@ -1023,6 +1026,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" },
|
{ url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wcwidth"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/38/75/2144b65e4fba12a2d9868e9a3f99db7fa0760670d064603634bef9ff1709/wcwidth-0.3.0.tar.gz", hash = "sha256:af1a2fb0b83ef4a7fc0682a4c95ca2576e14d0280bca2a9e67b7dc9f2733e123", size = 172238, upload-time = "2026-01-21T17:44:09.508Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/0e/a5f0257ab47492b7afb5fb60347d14ba19445e2773fc8352d4be6bd2f6f8/wcwidth-0.3.0-py3-none-any.whl", hash = "sha256:073a1acb250e4add96cfd5ef84e0036605cd6e0d0782c8c15c80e42202348458", size = 85520, upload-time = "2026-01-21T17:44:08.002Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "websockets"
|
name = "websockets"
|
||||||
version = "15.0.1"
|
version = "15.0.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user