90 Commits

Author SHA1 Message Date
kolo a6db733204 some fix 2025-04-13 13:13:03 +03:00
kolo 8506e4ffcf some fix 2025-04-12 18:07:10 +03:00
kolo 04d3329572 0.5.0-beta 2025-04-11 13:12:44 +03:00
kolo 5bfdde4bd9 now command aliases auto added to autocomlete history for their use 2025-04-10 19:23:57 +03:00
kolo e2dd7e4aea 0.5.0-alpha: support autocomplete, aliases for command, fix many bugs and other 2025-04-10 13:12:36 +03:00
kolo d1d644d422 final adding 2025-04-10 00:23:03 +03:00
kolo 8b496aa782 final work on autocomplete 2025-04-09 23:32:21 +03:00
kolo 592d128ef6 work on autocomplete 2025-04-09 12:16:30 +03:00
kolo b44ee227fd fix many bugs 2025-04-08 20:40:52 +03:00
kolo 0dce4a0d9e refactor app 2025-04-08 10:33:45 +03:00
kolo ca6634c6f0 some fix 2025-04-08 10:04:57 +03:00
kolo c1805af420 final work on dividing line 2025-04-08 00:28:17 +03:00
kolo 0e308ce77f work on dividing line 2025-04-07 19:44:51 +03:00
kolo ab1d335f8e work 2025-04-06 18:57:30 +03:00
kolo 1a2e9d1487 successful adding framed text 2025-04-05 09:58:02 +03:00
kolo 76bcba9340 some 2025-04-05 00:47:32 +03:00
kolo ae9795bd53 some fix and final refactor in readme 2025-04-04 01:43:59 +03:00
kolo 7540728f1b fix 2025-04-03 16:16:05 +03:00
kolo 54da63dd03 fix 2025-04-03 01:14:33 +03:00
kolo 8e08d0fe09 some fix 2025-04-03 01:09:03 +03:00
kolo 55b88f7c8a modified tests 2025-04-02 22:03:30 +03:00
kolo 1cd616336f refactor default view 2025-04-02 21:51:20 +03:00
kolo 253790fe2e work 2025-04-02 16:34:43 +03:00
kolo 1c6f896b73 work 2025-04-02 16:02:22 +03:00
kolo 8810e12551 linter workflow 2025-04-02 00:36:47 +03:00
kolo 285007a59a linter workflow 2025-04-02 00:34:34 +03:00
kolo 6edd17646a final workflow 2025-04-02 00:11:19 +03:00
kolo 154ee25dde test2 workflow 2025-04-02 00:09:48 +03:00
kolo 30cf3cfd06 test workflow 2025-04-02 00:06:48 +03:00
kolo 0d98d80919 Create tests.yml 2025-04-02 00:03:48 +03:00
kolo 54992e55cb some changes in readme 2025-04-01 23:49:17 +03:00
kolo cc8135b733 more working 2025-03-31 23:53:49 +03:00
kolo 5c6fa5151a new models, a model is passed to the command handler instead of a dictionary, removal of checks for intersection of processed triggers in handlers and much, much more 2025-03-31 19:14:42 +03:00
kolo 2918bc9f81 refactor, new model e.t.c. 2025-03-31 01:12:01 +03:00
kolo 6e2fbc23e9 adding __all__ in __init__ 2025-03-28 13:11:40 +03:00
kolo 1ec8ea53b4 new models, logic refactor, renaminf constants and other 2025-03-28 00:54:12 +03:00
kolo 4256d67789 v0.4.1 2025-03-27 00:17:40 +03:00
kolo 0246ff4b22 adding new method for App and new system tests 2025-03-24 10:44:49 +03:00
kolo 956febc757 v0.4.0 2025-03-15 20:50:44 +03:00
kolo beafdd0afd new system tests 2025-03-15 18:47:26 +03:00
kolo b172e2cdc3 new system tests 2025-03-15 11:42:12 +03:00
kolo baaf0e25f3 some fix 2025-03-15 00:08:39 +03:00
kolo 09c40978a1 fix 2025-03-12 13:01:15 +03:00
kolo 0f4f48555e fix 2025-03-12 00:51:11 +03:00
kolo d30515c1a2 v0.3.9 2025-03-11 20:44:06 +03:00
kolo 5a6fc1d8ca first step 2025-03-08 13:49:29 +03:00
kolo e5d6ead38e v0.3.8 2025-03-06 01:35:28 +03:00
kolo b61c151e1c v0.3.8 2025-03-06 01:35:18 +03:00
kolo a57ce490c1 v0.3.7 2025-03-06 00:15:39 +03:00
kolo edbd45f0bf some fix 2025-03-05 15:47:31 +03:00
kolo d59d274bca v0.3.6, final adding in docs 2025-03-05 02:27:10 +03:00
kolo 2bf2144815 work on docs, tests and some fix 2025-03-05 02:20:02 +03:00
kolo 971258728c work on docs 2025-03-04 18:44:45 +03:00
kolo 2c9c8da13c fix 2025-03-04 00:03:15 +03:00
kolo 404758bd91 fix 2025-03-03 18:52:49 +03:00
kolo 8fbf651223 v0.3.5 2025-03-03 14:26:37 +03:00
kolo 459c16ec87 v0.3.5 2025-03-03 14:26:13 +03:00
kolo d9c74310c3 start make tests 2025-03-03 00:10:07 +03:00
kolo 5e6cdc342e v0.3.4 2025-03-02 00:54:34 +03:00
kolo a378163431 v0.3.3 2025-03-01 16:44:33 +03:00
kolo fd4f2e1570 v0.3.2 2025-03-01 00:25:01 +03:00
kolo 3f7c577c29 0.3.1 2025-02-28 16:19:22 +03:00
kolo b72fcc6a11 fix 2025-02-27 01:16:39 +03:00
kolo 158e5eb75a fix 2025-02-25 00:37:11 +03:00
kolo 46cb2f70c5 fix 2025-02-24 19:04:20 +03:00
kolo ef242a5732 fix 2025-02-23 18:53:11 +03:00
kolo f87f102ced v0.3.0 2025-02-23 13:26:12 +03:00
kolo ddf2a2fb10 v0.3.0 2025-02-23 13:25:51 +03:00
kolo eac5358ead work on v0.3.0 2025-02-22 12:14:25 +03:00
kolo 76c18ddbff v0.3.0-alpha 2025-02-21 18:05:40 +03:00
kolo 4eeb4eb182 work on v0.3.0 2025-02-21 17:50:53 +03:00
kolo 905698384a work on v0.3.0 2025-02-21 01:46:03 +03:00
kolo 79ccfbb3b1 work on v0.3.0 2025-02-20 22:10:29 +03:00
kolo a63c46a78b work on v0.3.0 2025-02-19 23:49:38 +03:00
kolo a3a7cbf2e6 work on v0.3.0 2025-02-18 23:35:36 +03:00
kolo a9e545f3d8 work on v0.3.0 2025-02-17 20:57:15 +03:00
kolo 37b62fd69b fix 2025-02-17 00:33:05 +03:00
kolo 0ae86d0b2b work on v0.3.0 2025-02-15 12:05:42 +03:00
kolo ebfd5a80b3 work on v0.3.0 2025-02-13 23:26:01 +03:00
kolo 250704fc88 some fix 2025-02-13 19:31:30 +03:00
kolo 87239f1501 v0.2.2 2025-02-13 19:29:58 +03:00
kolo 48c117dd72 Merge branch 'kolo' of github.com:koloideal/Argenta into kolo 2025-02-13 19:26:12 +03:00
kolo 1d782f6213 fix 2025-02-13 19:26:08 +03:00
kolo ddf396a644 Update README.md 2025-02-13 17:34:15 +03:00
kolo c10af04280 Update README.md 2025-02-12 00:17:17 +03:00
kolo 890d863391 first version of docs 2025-02-12 00:13:43 +03:00
kolo 29b184b2ed working 2025-02-11 22:48:36 +03:00
kolo 6d331a57c1 working 2025-02-11 22:09:44 +03:00
kolo b0eb1e3e6c working 2025-02-11 21:36:23 +03:00
kolo 2d088caaaf work on readme 2025-02-11 01:21:27 +03:00
65 changed files with 2233 additions and 1220 deletions
+30
View File
@@ -0,0 +1,30 @@
name: ruff
on:
push:
branches: [ "kolo" ]
pull_request:
branches: [ "kolo" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
with:
python-version: "3.13"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install ruff
- name: Run linter
run: ruff check ./argenta
+31
View File
@@ -0,0 +1,31 @@
name: tests
on:
push:
branches: [ "kolo" ]
pull_request:
branches: [ "kolo" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
with:
python-version: "3.13"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install poetry
poetry install
- name: Run tests
run: poetry run python -m unittest discover
+4 -11
View File
@@ -1,14 +1,7 @@
.venv
.idea
argenta/router/__pycache__/
argenta/app/__pycache__/
argenta/__pycache__/
dist
tests/__pycache__
tests/mock_default_app/handlers/__pycache__/
tests/mock_default_app/__pycache__/
tests/mock_app/local_data_func/__pycache__/
tests/mock_app/handlers/handlers_implementation/__pycache__/
tests/mock_app/handlers/__pycache__/
tests/mock_app/business_logic/__pycache__/
tests/mock_app/__pycache__/
poetry.lock
*__pycache__/
*.hist*
+582 -1
View File
@@ -1,2 +1,583 @@
# Argenta
Python library for creating cli apps
---
## Описание
**Argenta** — Python library for creating TUI
![preview](https://github.com/koloideal/Argenta/blob/kolo/imgs/mock_app_preview_last.png?raw=True)
Пример внешнего вида TUI, написанного с помощью Argenta
---
# Установка
```bash
pip install argenta
```
or
```bash
poetry add argenta
```
---
# Быстрый старт
Пример простейшей оболочки с командой без зарегистрированных флагов
```python
# routers.py
from argenta.router import Router
from argenta.command import Command
router = Router()
@router.command(Command("hello"))
def handler():
print("Hello, world!")
```
```python
# main.py
from argenta.app import App
from routers import router
app: App = App()
def main() -> None:
app.include_router(router)
app.start_polling()
if __name__ == '__main__':
main()
```
Пример оболочки с командой, у которой зарегистрированы флаги
```python
# routers.py
import re
from argenta.router import Router
from argenta.command import Command
from argenta.command.flag import Flags, Flag, InputFlags
router = Router()
registered_flags = Flags(
Flag(name='host',
prefix='--',
possible_values=re.compile(r'^192.168.\d{1,3}.\d{1,3}$')),
Flag('port', '--', re.compile(r'^[0-9]{1,4}$')))
@router.command(Command("hello"))
def handler():
print("Hello, world!")
@router.command(Command(trigger="ssh",
description='connect via ssh',
flags=registered_flags))
def handler_with_flags(flags: InputFlags):
for flag in flags:
print(f'Flag name: {flag.get_name()}\n'
f'Flag value: {flag.get_value()}')
```
---
# *classes* :
---
## *class* : : `App`
Класс, определяющий поведение и состояние оболочки
### Конструктор
```python
App(prompt: str = '[italic dim bold]What do you want to do?\n',
initial_message: str = '\nArgenta\n',
farewell_message: str = '\nSee you\n',
exit_command: Command = Command('Q', 'Exit command'),
system_points_title: str | None = 'System points:',
ignore_command_register: bool = True,
dividing_line: StaticDividingLine | DynamicDividingLine = StaticDividingLine(),
repeat_command_groups: bool = True,
override_system_messages: bool = False,
autocompleter: AutoCompleter = AutoCompleter(),
print_func: Callable[[str], None] = Console().print)
```
**Аргументы:**
- **name : mean**
- `prompt` (`str`): Сообщение перед вводом команды.
- `initial_message` (`str`): Приветственное сообщение при запуске.
- `farewell_message` (`str`): Сообщение при выходе.
- `exit_command` (`Command`): Сущность команды, которая будет отвечать за завершение работы.
- `system_points_title` (`str`): Заголовок перед списком системных команд.
- `ignore_command_register` (`bool`): Игнорировать регистр всех команд.
- `dividing_line` (`StaticDividingLine | DynamicDividingLine`): Разделительная строка.
- `repeat_command_groups` (`bool`): Повторять описание команд перед вводом.
- `override_system_messages` (`bool`): Переопределить ли дефолтное оформление сообщений ([подробнее см.](#override_defaults))
- `autocompleter` (`AutoCompleter`): Сущность автодополнителя ввода.
- `print_func` (`Callable[[str], None]`): Функция вывода текста в терминал.
---
### ***methods***
---
#### **.start_polling() -> `None`**
*method mean* **::** Запускает цикл обработки ввода
---
#### **.include_router(router: Router) -> `None`**
*param* `router: Router` **::** Регистрируемый роутер
*required* **::** True
*method mean* **::** Регистрирует роутер в оболочке
---
#### **.include_routers(\*routers: Router) -> `None`**
*param* `routers: Router` **::** Неограниченное количество регистрируемых роутеров
*required* **::** True
*method mean* **::** Регистрирует роутер в оболочке
---
#### **.set_description_message_pattern(pattern: str) -> `None`**
*param* `pattern: str` **::** Паттерн описания команды при её выводе в консоль
*required* **::** True
*example* **::** `"[{command}] *=*=* {description}"`
*method mean* **::** Устанавливает паттерн описания команд, который будет использован
при выводе в консоль
---
#### **.add_message_on_startup(message: str) -> `None`**
*param* `message: str` **::** Сообщение, которое будет выведено при запуске приложения
*required* **::** True
*example* **::** `Message on startup`
*method mean* **::** Устанавливает паттерн описания команд, который будет использован
при выводе в консоль
---
<a name="custom_handler"></a>
#### **.repeated_input_flags_handler: `Callable[[str], None])`**
*example* **::** `lambda raw_command: print_func(f'Repeated input flags: "{raw_command}"')`
*attr mean* **::** Устанавливает функцию, которой будет передано управление при
вводе юзером повторяющихся флагов
---
#### **.invalid_input_flags_handler: `Callable[[str], None])`**
*example* **::** `lambda raw_command: print_func(f'Incorrect flag syntax: "{raw_command}"')`
*attr mean* **::** Устанавливает функцию, которой будет передано управление при
вводе юзером команды с некорректным синтаксисом флагов
---
#### **.unknown_command_handler: `Callable[[str], None]`**
*example* **::** `lambda command: print_func(f"Unknown command: {command.get_string_entity()}")`
*attr mean* **::** Устанавливает функцию, которой будет передано управление при
вводе юзером неизвестной команды
---
#### **.empty_command_handler: `Callable[[str], None])`**
*example* **::** `lambda: print_func(f'Empty input command')`
*attr mean* **::** Устанавливает функцию, которой будет передано управление при
вводе юзером пустой команды
---
### Примечания
- В устанавливаемом паттерне сообщения описания команды необходимы быть два ключевых слова:
`command` и `description`, каждое из которых должно быть заключено в фигурные скобки, после обработки
паттерна на места этих ключевых слов будут подставлены соответствующие атрибуты команды, при отсутствии
этих двух ключевых слов будет вызвано исключение `InvalidDescriptionMessagePatternException`
- Команды оболочки не должны повторяться, при значении атрибута `ignore_command_register` равным `True`
допускается создание обработчиков для разных регистров одинаковых символов в команде, для примера `u` и `U`,
при значении атрибута `ignore_command_register` класса `App` равным `False` тот же пример вызывает исключение
`RepeatedCommandInDifferentRoutersException`. Исключение вызывается только при наличии пересекающихся команд
у __<u>разных</u>__ роутеров
- Наиболее частые сообщение при запуске предопределены и доступны для быстрого
использования: `argenta.app.defaults.PredeterminedMessages`
<a name="override_defaults"></a>
- Если `override_system_messages`=`False`, то при переопределении таких атрибутов как `initial_message` и
`farawell_message` будет использовано дефолтное оформление текста, в виде красного ascii арта, при значении
`override_system_messages`=`True` системные сообщения будут отображены в точности какими были переданы
### Исключения
- `InvalidRouterInstanceException` — Переданный объект в метод `App().include_router()` не является экземпляром класса `Router`.
- `InvalidDescriptionMessagePatternException` — Неправильный формат паттерна описания команд.
- `IncorrectNumberOfHandlerArgsException` — У обработчика нестандартного поведения зарегистрировано неверное количество аргументов(в большинстве случаев у него должен быть один аргумент).
- `NoRegisteredHandlersException` — У роутера нет ни одного обработчика команд.
---
## *class* :: `AutoCompleter`
Класс, экземпляр которого представляет собой автодополнитель ввода
### Конструктор
```python
AutoCompleter(history_filename: str = False,
autocomplete_button: str = 'tab')
```
**Аргументы:**
- **name : mean**
- `history_filename` (`str` | `False`): Путь к файлу, который будет являться или является
историй пользовательского ввода, в последующем эти команды будут автодополняться, файл
может не существовать при инициализации, тогда он будет создан, при значении аргумента `False`
история пользовательского ввода будет существовать только в пределах сессии и не сохраняться в файл
- `autocomplete_button` (`str`): Строковое обозначение кнопки на клавиатуре, которая будет
использоваться для автодополнения при вводе, по умолчанию `tab`
---
## *class* :: `StaticDivideLine`
Класс, экземпляр которого представляет собой строковый разделитель фиксированной длины
### Конструктор
```python
StaticDivideLine(unit_part: str = '-',
length: int = 25)
```
**Аргументы:**
- **name : mean**
- `unit_part` (`str`): Единичная часть строкового разделителя
- `length` (`int`): Длина строкового разделителя
---
## *class* :: `DinamicDivideLine`
Строковый разделитель динамической длины, которая определяется длиной обрамляемого вывода команды
### Конструктор
```python
DinamicDivideLine(unit_part: str = '-')
```
**Аргументы:**
- **name : mean**
- `unit_part` (`str`): Единичная часть строкового разделителя
---
## *class* :: `Router`
Класс, который определяет и конфигурирует обработчики команд
### Конструктор
```python
Router(title: str | None = None,
name: str = 'Default')
```
**Аргументы:**
- **name : mean**
- `title` (`str`): Заголовок группы команд.
- `name` (`str`): Персональное название роутера
---
### ***methods***
---
#### **command(command: Command)**
*param* `command: Command` **::** Экземпляр класса `Command`, который определяет строковый триггер команды,
допустимые флаги команды и другое
*required* **::** True
*example* **::** `Command(command='ssh', description='connect via ssh')`
*method mean* **::** Декоратор, который регистрирует функцию как обработчик команды
---
#### **.get_name() -> `str`**
*method mean* **::** Возвращает установленное название роутера
---
#### **.get_title() -> `str`**
*method mean* **::** Возвращает установленный заголовок группы команд данного роутера
---
### Исключения
- `RepeatedFlagNameException` - Повторяющиеся зарегистрированные флаги в команде
- `TooManyTransferredArgsException` - Слишком много зарегистрированных аргументов у обработчика команды
- `RequiredArgumentNotPassedException` - Не зарегистрирован обязательный аргумент у обработчика команды(аргумент, через который будут переданы флаги введённой команды)
- `IncorrectNumberOfHandlerArgsException` - У обработчика нестандартного поведения зарегистрировано неверное количество аргументов(в большинстве случаев у него должен быть один аргумент)
- `TriggerCannotContainSpacesException` - У регистрируемой команды в триггере содержатся пробелы
---
## *class* :: `Command`
Класс, экземпляр которого определяет строковый триггер хэндлера и конфигурирует его атрибуты
### Конструктор
```python
Command(trigger: str,
description: str = None,
flags: Flag | Flags = None)
```
**Аргументы:**
- **name : mean**
- `trigger` (`str`): Строковый триггер
- `description` (`str`): Описание команды, которое будет выведено в консоль при запуске оболочки
- `flags` (`Flag | Flags`): Флаги, которые будут обработаны при их наличии во вводе юзера
---
#### **.get_trigger() -> `str`**
*method mean* **::** Возвращает строковый триггер экземпляра
---
#### **.get_description() -> `str`**
*method mean* **::** Возвращает описание команды
---
#### **.get_registered_flags() -> `Flags | None`**
*method mean* **::** Возвращает зарегистрированные флаги экземпляра
---
### Исключения
- `UnprocessedInputFlagException` - Некорректный синтаксис ввода команды
- `RepeatedInputFlagsException` - Повторяющиеся флаги во введённой команде
- `EmptyInputCommandException` - Введённая команда является пустой(не содержит символов)
**Примечание**
Все вышеуказанные исключения класса `Command` вызываются в рантайме запущенным экземпляром класса
`App`, а также по дефолту обрабатываются, при желании можно задать пользовательские
обработчики для этих исключений ([подробнее см.](#custom_handler))
---
## *class* :: `Flag`
Класс, экземпляры которого в большинстве случаев передаются при создании
экземпляра класса `Command` для регистрации допустимого флага при вводе юзером команды
### Конструктор
```python
Flag(name: str,
prefix: typing.Literal['-', '--', '---'] = '-',
possible_values: list[str] | typing.Pattern[str] | False = True)
```
---
**Аргументы:**
- **name : mean**
- `name` (`str`): Имя флага
- `prefix` (`Literal['-', '--', '---']`): Префикс команды, допустимым значением является от одного до трёх минусов
- `possible_values` (`list[str] | Pattern[str] | bool`): Множество допустимых значений флага, может быть задано
списком с допустимыми значениями или регулярным выражением (рекомендуется `re.compile(r'example exspression')`), при значении
аргумента `False` у введённого флага не может быть значения, иначе будет вызвано исключение и обработано соответствующим
еррор-хэндлером
---
### ***methods***
---
#### **.get_string_entity() -> `str`**
*method mean* **::** Возвращает строковое представление флага(префикс + имя)
---
#### **.get_name() -> `str`**
*method mean* **::** Возвращает имя флага
---
#### **.get_prefix() -> `str`**
*method mean* **::** Возвращает префикс флага
---
## *class* :: `InputFlag`
Класс, экземпляры которого являются введёнными флагами команды, передаётся в хэндлер команды
через `InputFlags`
---
### Примечания
- Наиболее часто используемые флаги предопределены и доступны для быстрого использования:
`argenta.command.flag.defaults.PredeterminedFlags`
---
### Конструктор
```python
InputFlag(name: str,
prefix: typing.Literal['-', '--', '---'] = '-',
value: str = None)
```
---
**Аргументы:**
- **name : mean**
- `name` (`str`): Имя флага
- `prefix` (`Literal['-', '--', '---']`): Префикс команды, допустимым значением является от одного до трёх минусов
- `value` (`str`): Значение введённого флага, если оно есть
---
### ***methods***
---
#### **.get_value() -> `str | None`**
*method mean* **::** Возвращает значение введённого флага
---
## *class* :: `Flags`
Класс, объединяющий список флагов в один объект, используется в качестве
передаваемого аргумента `flags` экземпляру класса `Command`, при регистрации
хэндлера
### Конструктор
```python
Flags(*flags: Flag)
```
---
**Аргументы:**
- **name : mean**
- `*flags` (`Flag`): Неограниченное количество передаваемых флагов
---
### ***methods***
---
#### **.get_flags() -> `list[Flag]`**
*method mean* **::** Возвращает зарегистрированные флаги
---
#### **.add_flag(flag: Flag) -> `None`**
*method mean* **::** Добавляет флаг в группу
---
#### **.add_flags(flags: list[Flag]) -> `None`**
*method mean* **::** Добавляет флаги в группу
---
#### **.get_flag(name: str) -> `Flag | None`**
*param* `name: str` **::** Строковый триггер флага без префикса
*required* **::** True
*example* **::** `'host'`
*method mean* **::** Возвращает флаг по его триггеру или `None`, если флаг не найден
---
## *class* :: `InputFlags`
Класс, объединяющий список введённых флагов в один объект, передаётся соответствующему хэндлеру
в качестве аргумента
### Конструктор
```python
InputFlags(*flags: Flag)
```
---
**Аргументы:**
- **name : mean**
- `*flags` (`InputFlag`): Неограниченное количество передаваемых флагов
---
### ***methods***
---
#### **.get_flags() -> `list[Flag]`**
*method mean* **::** Возвращает введённые флаги
---
#### **.get_flag(name: str) -> `InputFlag | None`**
*param* `name: str` **::** Строковый триггер флага без префикса
*required* **::** True
*example* **::** `'host'`
*method mean* **::** Возвращает введённый флаг по его триггеру или `None`, если флаг не найден
---
# Тесты
Запуск тестов:
```bash
python -m unittest discover
```
or
```bash
python -m unittest discover -v
```
+3 -7
View File
@@ -1,7 +1,3 @@
from .entity import App
from .exceptions import (HandlerForUnknownCommandsCanOnlyBeDeclaredForMainRouterException,
InvalidDescriptionMessagePatternException,
InvalidRouterInstanceException,
OnlyOneMainRouterIsAllowedException,
MissingMainRouterException,
MissingHandlersForUnknownCommandsOnMainRouterException)
__all__ = ["App"]
from argenta.app.models import App
+4
View File
@@ -0,0 +1,4 @@
__all__ = ["AutoCompleter"]
from argenta.app.autocompleter.entity import AutoCompleter
+47
View File
@@ -0,0 +1,47 @@
import os
import readline
class AutoCompleter:
def __init__(self, history_filename: str = False, autocomplete_button: str = 'tab'):
self.history_filename = history_filename
self.autocomplete_button = autocomplete_button
self.matches = []
def complete(self, text, state):
matches = sorted(cmd for cmd in self.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: list[str]):
if self.history_filename:
if os.path.exists(self.history_filename):
readline.read_history_file(self.history_filename)
else:
for line in all_commands:
readline.add_history(line)
readline.set_completer(self.complete)
readline.set_completer_delims(readline.get_completer_delims().replace(' ', ''))
readline.parse_and_bind(f'{self.autocomplete_button}: complete')
def exit_setup(self):
if self.history_filename:
readline.write_history_file(self.history_filename)
@staticmethod
def get_history_items():
return [readline.get_history_item(i) for i in range(1, readline.get_current_history_length() + 1)]
+9
View File
@@ -0,0 +1,9 @@
from dataclasses import dataclass
@dataclass
class PredeterminedMessages:
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]'
AUTOCOMPLETE = '[b dim]Autocomplete[/b dim]: [i]<part>[/i] [bold]<tab>'
+4
View File
@@ -0,0 +1,4 @@
__all__ = ["StaticDividingLine", "DynamicDividingLine"]
from argenta.app.dividing_line.models import StaticDividingLine, DynamicDividingLine
+24
View File
@@ -0,0 +1,24 @@
class BaseDividingLine:
def __init__(self, unit_part: str = '-'):
self.unit_part = unit_part
def get_unit_part(self):
if len(self.unit_part) == 0:
return ' '
else:
return self.unit_part[0]
class StaticDividingLine(BaseDividingLine):
def __init__(self, unit_part: str = '-', length: int = 25):
super().__init__(unit_part)
self.length = length
def get_full_line(self):
return f'\n[dim]{self.length * self.get_unit_part()}[/dim]\n'
class DynamicDividingLine(BaseDividingLine):
def get_full_line(self, length: int):
return f'\n[dim]{self.get_unit_part() * length}[/dim]\n'
-220
View File
@@ -1,220 +0,0 @@
from typing import Callable
from ..router.entity import Router
from .exceptions import (InvalidRouterInstanceException,
InvalidDescriptionMessagePatternException,
OnlyOneMainRouterIsAllowedException,
MissingMainRouterException,
MissingHandlersForUnknownCommandsOnMainRouterException,
HandlerForUnknownCommandsCanOnlyBeDeclaredForMainRouterException,
NoRegisteredRoutersException,
NoRegisteredHandlersException,
RepeatedCommandInDifferentRoutersException)
class App:
def __init__(self,
prompt: str = 'Enter a command',
initial_greeting: str = '\nHello, I am Argenta\n',
farewell_message: str = '\nGoodBye\n',
exit_command: str = 'Q',
exit_command_description: str = 'Exit command',
exit_command_title: str = 'System points:',
ignore_exit_command_register: bool = True,
ignore_command_register: bool = False,
line_separate: str = '',
command_group_description_separate: str = '',
repeat_command_groups: bool = True,
print_func: Callable[[str], None] = print) -> None:
self.prompt = prompt
self.print_func = print_func
self.exit_command = exit_command
self.exit_command_description = exit_command_description
self.exit_command_title = exit_command_title
self.ignore_exit_command_register = ignore_exit_command_register
self.farewell_message = farewell_message
self.initial_greeting = initial_greeting
self.line_separate = line_separate
self.command_group_description_separate = command_group_description_separate
self.ignore_command_register = ignore_command_register
self.repeat_command_groups = repeat_command_groups
self._routers: list[Router] = []
self._registered_router_entities: list[dict[str, str | list[dict[str, Callable[[], None] | str]] | Router]] = []
self._app_main_router: Router | None = None
self._description_message_pattern: str = '[{command}] *=*=* {description}'
def start_polling(self) -> None:
self._validate_number_of_routers()
self._validate_included_routers()
self._validate_main_router()
self._validate_all_router_commands()
self.print_func(self.initial_greeting)
if not self.repeat_command_groups:
self._print_command_group_description()
if self.repeat_command_groups:
self.print_func(self.prompt)
while True:
if self.repeat_command_groups:
self._print_command_group_description()
self.print_func(self.prompt)
command: str = input()
self._checking_command_for_exit_command(command)
self.print_func(self.line_separate)
is_unknown_command: bool = self._check_is_command_unknown(command)
if is_unknown_command:
continue
for router in self._routers:
router.input_command_handler(command)
self.print_func(self.line_separate)
self.print_func(self.command_group_description_separate)
def set_initial_greeting(self, greeting: str) -> None:
self.initial_greeting: str = greeting
def set_farewell_message(self, message: str) -> None:
self.farewell_message: str = message
def set_description_message_pattern(self, pattern: str) -> None:
try:
pattern.format(command='command',
description='description')
except KeyError:
raise InvalidDescriptionMessagePatternException(pattern)
self._description_message_pattern: str = pattern
def get_main_router(self) -> Router:
return self._app_main_router
def get_all_app_commands(self) -> list[str]:
all_commands: list[str] = []
for router in self._routers:
all_commands.extend(router.get_all_commands())
return all_commands
def _validate_number_of_routers(self) -> None:
if not self._routers:
raise NoRegisteredRoutersException()
def _validate_included_routers(self) -> None:
for router in self._routers:
if not router.get_command_entities():
raise NoRegisteredHandlersException(router.get_name())
def _validate_main_router(self):
if not self._app_main_router:
if len(self._routers) > 1:
raise MissingMainRouterException()
else:
router = self._routers[0]
router.set_router_as_main()
self._app_main_router = router
if not self._app_main_router.unknown_command_func:
raise MissingHandlersForUnknownCommandsOnMainRouterException()
for router in self._routers:
if router.unknown_command_func and self._app_main_router is not router:
raise HandlerForUnknownCommandsCanOnlyBeDeclaredForMainRouterException()
def _validate_all_router_commands(self) -> None:
for idx in range(len(self._registered_router_entities)):
current_router: Router = self._registered_router_entities[idx]['entity']
routers_without_current_router = self._registered_router_entities.copy()
routers_without_current_router.pop(idx)
current_router_all_commands: list[str] = current_router.get_all_commands()
for router_entity in routers_without_current_router:
if len(set(current_router_all_commands).intersection(set(router_entity['entity'].get_all_commands()))) > 0:
raise RepeatedCommandInDifferentRoutersException()
if self.ignore_command_register:
if len(set([x.lower() for x in current_router_all_commands]).intersection(set([x.lower() for x in router_entity['entity'].get_all_commands()]))) > 0:
raise RepeatedCommandInDifferentRoutersException()
def _checking_command_for_exit_command(self, command: str):
if command.lower() == self.exit_command.lower():
if self.ignore_exit_command_register:
self.print_func(self.farewell_message)
exit(0)
else:
if command == self.exit_command:
self.print_func(self.farewell_message)
exit(0)
def _check_is_command_unknown(self, command: str):
registered_router_entities: list[dict[str, str | list[dict[str, Callable[[], None] | str]] | Router]] = self._registered_router_entities
for router_entity in registered_router_entities:
for command_entity in router_entity['commands']:
if command_entity['command'].lower() == command.lower():
if self.ignore_command_register:
return False
else:
if command_entity['command'] == command:
return False
self._app_main_router.unknown_command_handler(command)
self.print_func(self.line_separate)
self.print_func(self.command_group_description_separate)
return True
def _print_command_group_description(self):
for router_entity in self._registered_router_entities:
self.print_func(router_entity['title'])
for command_entity in router_entity['commands']:
self.print_func(self._description_message_pattern.format(
command=command_entity['command'],
description=command_entity['description']
)
)
self.print_func(self.command_group_description_separate)
self.print_func(self.exit_command_title)
self.print_func(self._description_message_pattern.format(
command=self.exit_command,
description=self.exit_command_description
)
)
self.print_func(self.command_group_description_separate)
def include_router(self, router: Router, is_main: True | False = False) -> None:
if not isinstance(router, Router):
raise InvalidRouterInstanceException()
if is_main:
if not self._app_main_router:
self._app_main_router = router
router.set_router_as_main()
else:
raise OnlyOneMainRouterIsAllowedException(self._app_main_router.get_name())
router.set_ignore_command_register(self.ignore_command_register)
self._routers.append(router)
command_entities: list[dict[str, Callable[[], None] | str]] = router.get_command_entities()
self._registered_router_entities.append({'name': router.get_name(),
'title': router.get_title(),
'entity': router,
'commands': command_entities})
-46
View File
@@ -1,44 +1,3 @@
class InvalidRouterInstanceException(Exception):
def __str__(self):
return "Invalid Router Instance"
class InvalidDescriptionMessagePatternException(Exception):
def __init__(self, pattern: str):
self.pattern = pattern
def __str__(self):
return ("Invalid Description Message Pattern\n"
"Correct pattern example: [{command}] *=*=* {description}\n"
"The pattern must contain two variables: `command` and `description` - description of the command\n"
f"Your pattern: {self.pattern}")
class OnlyOneMainRouterIsAllowedException(Exception):
def __init__(self, existing_main_router):
self.existing_main_router = existing_main_router
def __str__(self):
return ("Only One Main Router Allowed\n"
f"Existing main router is: {self.existing_main_router}")
class MissingMainRouterException(Exception):
def __str__(self):
return ("Missing Main Router\n"
"One of the registered routers must be the main one")
class MissingHandlersForUnknownCommandsOnMainRouterException(Exception):
def __str__(self):
return ("Missing Handlers For Unknown Commands On The Main Router\n"
"The main router must have a declared handler for unknown commands")
class HandlerForUnknownCommandsCanOnlyBeDeclaredForMainRouterException(Exception):
def __str__(self):
return '\nThe handler for unknown commands can only be declared for the main router'
class NoRegisteredRoutersException(Exception):
def __str__(self):
return "No Registered Router Found"
@@ -49,8 +8,3 @@ class NoRegisteredHandlersException(Exception):
self.router_name = router_name
def __str__(self):
return f"No Registered Handlers Found For '{self.router_name}'"
class RepeatedCommandInDifferentRoutersException(Exception):
def __str__(self):
return "Commands in different handlers cannot be repeated"
+265
View File
@@ -0,0 +1,265 @@
from typing import Callable
from rich.console import Console
from rich.markup import escape
from art import text2art
from contextlib import redirect_stdout
import io
import re
from argenta.command.models import Command, InputCommand
from argenta.router import Router
from argenta.router.defaults import system_router
from argenta.app.autocompleter import AutoCompleter
from argenta.app.dividing_line.models import StaticDividingLine, DynamicDividingLine
from argenta.command.exceptions import (UnprocessedInputFlagException,
RepeatedInputFlagsException,
EmptyInputCommandException,
BaseInputCommandException)
from argenta.app.exceptions import (NoRegisteredRoutersException,
NoRegisteredHandlersException)
from argenta.app.registered_routers.entity import RegisteredRouters
class AppInit:
def __init__(self,
prompt: str = '[italic dim bold]What do you want to do?\n',
initial_message: str = '\nArgenta\n',
farewell_message: str = '\nSee you\n',
exit_command: Command = Command('Q', 'Exit command'),
system_points_title: str | None = 'System points:',
ignore_command_register: bool = True,
dividing_line: StaticDividingLine | DynamicDividingLine = StaticDividingLine(),
repeat_command_groups: bool = True,
override_system_messages: bool = False,
autocompleter: AutoCompleter = AutoCompleter(),
print_func: Callable[[str], None] = Console().print) -> None:
self._prompt = prompt
self._print_func = print_func
self._exit_command = exit_command
self._system_points_title = system_points_title
self._dividing_line = dividing_line
self._ignore_command_register = ignore_command_register
self._repeat_command_groups_description = repeat_command_groups
self._override_system_messages = override_system_messages
self._autocompleter = autocompleter
self._farewell_message = farewell_message
self._initial_message = initial_message
self._description_message_gen: Callable[[str, str], str] = lambda command, description: f'[bold red]{escape('['+command+']')}[/bold red] [blue dim]*=*=*[/blue dim] [bold yellow italic]{escape(description)}'
self._registered_routers: RegisteredRouters = RegisteredRouters()
self._messages_on_startup = []
self._invalid_input_flags_handler: Callable[[str], None] = lambda raw_command: print_func(f'[red bold]Incorrect flag syntax: {escape(raw_command)}')
self._repeated_input_flags_handler: Callable[[str], None] = lambda raw_command: print_func(f'[red bold]Repeated input flags: {escape(raw_command)}')
self._empty_input_command_handler: Callable[[], None] = lambda: print_func('[red bold]Empty input command')
self._unknown_command_handler: Callable[[InputCommand], None] = lambda command: print_func(f"[red bold]Unknown command: {escape(command.get_trigger())}")
self._exit_command_handler: Callable[[], None] = lambda: print_func(self._farewell_message)
class AppSetters(AppInit):
def set_description_message_pattern(self, pattern: Callable[[str, str], str]) -> None:
self._description_message_gen: Callable[[str, str], str] = pattern
def set_invalid_input_flags_handler(self, handler: Callable[[str], None]) -> None:
self._invalid_input_flags_handler = handler
def set_repeated_input_flags_handler(self, handler: Callable[[str], None]) -> None:
self._repeated_input_flags_handler = handler
def set_unknown_command_handler(self, handler: Callable[[str], None]) -> None:
self._unknown_command_handler = handler
def set_empty_command_handler(self, handler: Callable[[], None]) -> None:
self._empty_input_command_handler = handler
def set_exit_command_handler(self, handler: Callable[[], None]) -> None:
self._exit_command_handler = handler
class AppPrinters(AppInit):
def _print_command_group_description(self):
for registered_router in self._registered_routers:
if registered_router.get_title():
self._print_func(registered_router.get_title())
for command_handler in registered_router.get_command_handlers():
self._print_func(self._description_message_gen(
command_handler.get_handled_command().get_trigger(),
command_handler.get_handled_command().get_description()))
self._print_func('')
def _print_framed_text_with_dynamic_line(self, text: str):
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_length_line if 10 <= max_length_line <= 80 else 80 if max_length_line > 80 else 10
self._print_func(self._dividing_line.get_full_line(max_length_line))
print(text.strip('\n'))
self._print_func(self._dividing_line.get_full_line(max_length_line))
def _print_framed_text(self, text: str):
if isinstance(self._dividing_line, StaticDividingLine):
self._print_func(self._dividing_line.get_full_line())
self._print_func(text)
self._print_func(self._dividing_line.get_full_line())
elif isinstance(self._dividing_line, DynamicDividingLine):
self._print_framed_text_with_dynamic_line(text)
class AppNonStandardHandlers(AppPrinters):
def _is_exit_command(self, command: InputCommand):
if command.get_trigger().lower() == self._exit_command.get_trigger().lower():
if self._ignore_command_register:
system_router.input_command_handler(command)
return True
elif command.get_trigger() == self._exit_command.get_trigger():
system_router.input_command_handler(command)
return True
return False
def _is_unknown_command(self, command: InputCommand):
for router_entity in self._registered_routers:
for command_handler in router_entity.get_command_handlers():
handled_command_trigger = command_handler.get_handled_command().get_trigger()
handled_command_aliases = command_handler.get_handled_command().get_aliases()
if handled_command_trigger.lower() == command.get_trigger().lower() and self._ignore_command_register:
return False
elif handled_command_trigger == command.get_trigger():
return False
elif handled_command_aliases:
if (command.get_trigger().lower() in [x.lower() for x in handled_command_aliases]) and self._ignore_command_register:
return False
elif command.get_trigger() in handled_command_trigger:
return False
if isinstance(self._dividing_line, StaticDividingLine):
self._print_func(self._dividing_line.get_full_line())
self._unknown_command_handler(command)
self._print_func(self._dividing_line.get_full_line())
elif isinstance(self._dividing_line, DynamicDividingLine):
with redirect_stdout(io.StringIO()) as f:
self._unknown_command_handler(command)
res: str = f.getvalue()
self._print_framed_text_with_dynamic_line(res)
return True
def _error_handler(self, error: BaseInputCommandException, raw_command: str) -> None:
match error:
case UnprocessedInputFlagException():
self._invalid_input_flags_handler(raw_command)
case RepeatedInputFlagsException():
self._repeated_input_flags_handler(raw_command)
case EmptyInputCommandException():
self._empty_input_command_handler()
class AppValidators(AppInit):
def _validate_number_of_routers(self) -> None:
if not self._registered_routers:
raise NoRegisteredRoutersException()
def _validate_included_routers(self) -> None:
for router in self._registered_routers:
if not router.get_command_handlers():
raise NoRegisteredHandlersException(router.get_name())
class AppSetups(AppValidators, AppPrinters):
def _setup_system_router(self):
system_router.set_title(self._system_points_title)
@system_router.command(self._exit_command)
def exit_command():
self._exit_command_handler()
if system_router not in self._registered_routers.get_registered_routers():
system_router.set_ignore_command_register(self._ignore_command_register)
self._registered_routers.add_registered_router(system_router)
def _setup_default_view(self):
if not self._override_system_messages:
self._initial_message = f'\n[bold red]{text2art(self._initial_message, font='tarty1')}\n\n'
self._farewell_message = (
f'[bold red]\n{text2art(f'\n{self._farewell_message}\n', font='chanky')}[/bold red]\n'
f'[red i]github.com/koloideal/Argenta[/red i] | [red bold i]made by kolo[/red bold i]\n')
def _pre_cycle_setup(self):
self._setup_default_view()
self._setup_system_router()
self._validate_number_of_routers()
self._validate_included_routers()
all_triggers: list[str] = []
for router_entity in self._registered_routers:
all_triggers.extend(router_entity.get_triggers())
all_triggers.extend(router_entity.get_aliases())
self._autocompleter.initial_setup(all_triggers)
self._print_func(self._initial_message)
for message in self._messages_on_startup:
self._print_func(message)
print('\n\n')
if not self._repeat_command_groups_description:
self._print_command_group_description()
class App(AppSetters, AppNonStandardHandlers, AppSetups):
def start_polling(self) -> None:
self._pre_cycle_setup()
while True:
if self._repeat_command_groups_description:
self._print_command_group_description()
raw_command: str = Console().input(self._prompt)
try:
input_command: InputCommand = InputCommand.parse(raw_command=raw_command)
except BaseInputCommandException as error:
with redirect_stdout(io.StringIO()) as f:
self._error_handler(error, raw_command)
res: str = f.getvalue()
self._print_framed_text(res)
continue
if self._is_exit_command(input_command):
self._autocompleter.exit_setup()
return
if self._is_unknown_command(input_command):
continue
with redirect_stdout(io.StringIO()) as f:
for registered_router in self._registered_routers:
registered_router.input_command_handler(input_command)
res: str = f.getvalue()
self._print_framed_text(res)
if not self._repeat_command_groups_description:
self._print_func(self._prompt)
def include_router(self, router: Router) -> None:
router.set_ignore_command_register(self._ignore_command_register)
self._registered_routers.add_registered_router(router)
def include_routers(self, *routers: Router) -> None:
for router in routers:
self.include_router(router)
def add_message_on_startup(self, message: str) -> None:
self._messages_on_startup.append(message)
+21
View File
@@ -0,0 +1,21 @@
from argenta.router import Router
class RegisteredRouters:
def __init__(self, registered_routers: list[Router] = None) -> None:
self._registered_routers = registered_routers if registered_routers else []
def get_registered_routers(self) -> list[Router]:
return self._registered_routers
def add_registered_router(self, router: Router):
self._registered_routers.append(router)
def add_registered_routers(self, *routers: Router):
self._registered_routers.extend(routers)
def __iter__(self):
return iter(self._registered_routers)
def __next__(self):
return next(iter(self._registered_routers))
+3
View File
@@ -0,0 +1,3 @@
__all__ = ["Command"]
from argenta.command.models import Command
+23
View File
@@ -0,0 +1,23 @@
from argenta.command.flag.models import InputFlag, Flag
class BaseInputCommandException(Exception):
pass
class UnprocessedInputFlagException(BaseInputCommandException):
def __str__(self):
return "Unprocessed Input Flags"
class RepeatedInputFlagsException(BaseInputCommandException):
def __init__(self, flag: Flag | InputFlag):
self.flag = flag
def __str__(self):
return ("Repeated Input Flags\n"
f"Duplicate flag was detected in the input: '{self.flag.get_string_entity()}'")
class EmptyInputCommandException(BaseInputCommandException):
def __str__(self):
return "Input Command is empty"
+4
View File
@@ -0,0 +1,4 @@
__all__ = ('InputFlags', 'InputFlag', 'Flag', 'Flags')
from argenta.command.flag.models import InputFlags, InputFlag, Flags, Flag
+21
View File
@@ -0,0 +1,21 @@
from dataclasses import dataclass
from argenta.command.flag.models import Flag
import re
@dataclass
class PredeterminedFlags:
HELP = Flag(name='help', possible_values=False)
SHORT_HELP = Flag(name='h', prefix='-', possible_values=False)
INFO = Flag(name='info', possible_values=False)
SHORT_INFO = Flag(name='i', prefix='-', possible_values=False)
ALL = Flag(name='all', possible_values=False)
SHORT_ALL = Flag(name='a', prefix='-', possible_values=False)
HOST = Flag(name='host', possible_values=re.compile(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$'))
SHORT_HOST = Flag(name='h', 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}$'))
SHORT_PORT = Flag(name='p', prefix='-', possible_values=re.compile(r'^\d{1,5}$'))
+140
View File
@@ -0,0 +1,140 @@
from typing import Literal, Pattern
from abc import ABC, abstractmethod
class BaseFlag:
def __init__(self, name: str,
prefix: Literal['-', '--', '---'] = '--'):
self._name = name
self._prefix = prefix
def get_string_entity(self):
string_entity: str = self._prefix + self._name
return string_entity
def get_name(self):
return self._name
def get_prefix(self):
return self._prefix
class InputFlag(BaseFlag):
def __init__(self, name: str,
prefix: Literal['-', '--', '---'] = '--',
value: str = None):
super().__init__(name, prefix)
self._flag_value = value
def get_value(self) -> str | None:
return self._flag_value
def set_value(self, value):
self._flag_value = value
class Flag(BaseFlag):
def __init__(self, name: str,
prefix: Literal['-', '--', '---'] = '--',
possible_values: list[str] | Pattern[str] | False = True):
super().__init__(name, prefix)
self.possible_values = possible_values
def validate_input_flag_value(self, input_flag_value: str | None):
if self.possible_values is False:
if input_flag_value is None:
return True
else:
return False
elif isinstance(self.possible_values, Pattern):
if isinstance(input_flag_value, str):
is_valid = bool(self.possible_values.match(input_flag_value))
if bool(is_valid):
return True
else:
return False
else:
return False
elif isinstance(self.possible_values, list):
if input_flag_value in self.possible_values:
return True
else:
return False
else:
return True
class BaseFlags(ABC):
__slots__ = ('_flags',)
@abstractmethod
def get_flags(self):
pass
@abstractmethod
def add_flag(self, flag: Flag | InputFlag):
pass
@abstractmethod
def add_flags(self, flags: list[Flag] | list[InputFlag]):
pass
@abstractmethod
def get_flag(self, name: str):
pass
def __iter__(self):
return iter(self._flags)
def __next__(self):
return next(iter(self))
def __getitem__(self, item):
return self._flags[item]
class Flags(BaseFlags, ABC):
def __init__(self, *flags: Flag):
self._flags = flags if flags else []
def get_flags(self) -> list[Flag]:
return self._flags
def add_flag(self, flag: Flag):
self._flags.append(flag)
def add_flags(self, flags: list[Flag]):
self._flags.extend(flags)
def get_flag(self, name: str) -> Flag | None:
if name in [flag.get_name() for flag in self._flags]:
return list(filter(lambda flag: flag.get_name() == name, self._flags))[0]
else:
return None
class InputFlags(BaseFlags, ABC):
def __init__(self, *flags: InputFlag):
self._flags = flags if flags else []
def get_flags(self) -> list[InputFlag]:
return self._flags
def add_flag(self, flag: InputFlag):
self._flags.append(flag)
def add_flags(self, flags: list[InputFlag]):
self._flags.extend(flags)
def get_flag(self, name: str) -> InputFlag | None:
if name in [flag.get_name() for flag in self._flags]:
return list(filter(lambda flag: flag.get_name() == name, self._flags))[0]
else:
return None
+112
View File
@@ -0,0 +1,112 @@
from argenta.command.flag.models import Flag, InputFlag, Flags, InputFlags
from argenta.command.exceptions import (UnprocessedInputFlagException,
RepeatedInputFlagsException,
EmptyInputCommandException)
from typing import Generic, TypeVar, cast, Literal
InputCommandType = TypeVar('InputCommandType')
class BaseCommand:
def __init__(self, trigger: str):
self._trigger = trigger
def get_trigger(self) -> str:
return self._trigger
class Command(BaseCommand):
def __init__(self, trigger: str,
description: str = None,
flags: Flag | Flags = None,
aliases: list[str] = None):
super().__init__(trigger)
self._registered_flags: Flags = flags if isinstance(flags, Flags) else Flags(flags) if isinstance(flags, Flag) else Flags()
self._description = f'Description for "{self._trigger}" command' if not description else description
self._aliases = aliases
def get_registered_flags(self) -> Flags:
return self._registered_flags
def get_aliases(self) -> list[str] | None:
return self._aliases
def validate_input_flag(self, flag: InputFlag):
registered_flags: Flags | None = self.get_registered_flags()
if registered_flags:
if isinstance(registered_flags, Flag):
if registered_flags.get_string_entity() == flag.get_string_entity():
is_valid = registered_flags.validate_input_flag_value(flag.get_value())
if is_valid:
return True
else:
for registered_flag in registered_flags:
if registered_flag.get_string_entity() == flag.get_string_entity():
is_valid = registered_flag.validate_input_flag_value(flag.get_value())
if is_valid:
return True
return False
def get_description(self) -> str:
return self._description
class InputCommand(BaseCommand, Generic[InputCommandType]):
def __init__(self, trigger: str,
input_flags: InputFlag | InputFlags = None):
super().__init__(trigger)
self._input_flags: InputFlags = input_flags if isinstance(input_flags, InputFlags) else InputFlags(input_flags) if isinstance(input_flags, InputFlag) else InputFlags()
def _set_input_flags(self, input_flags: InputFlags):
self._input_flags = input_flags
def get_input_flags(self) -> InputFlags:
return self._input_flags
@staticmethod
def parse(raw_command: str) -> InputCommandType:
if not raw_command:
raise EmptyInputCommandException()
list_of_tokens = raw_command.split()
command = list_of_tokens.pop(0)
input_flags: InputFlags = InputFlags()
current_flag_name, current_flag_value = None, None
for k, _ in enumerate(list_of_tokens):
if _.startswith('-'):
if current_flag_name or len(_) < 2 or len(_[:_.rfind('-')]) > 3:
raise UnprocessedInputFlagException()
current_flag_name = _
else:
if not current_flag_name:
raise UnprocessedInputFlagException()
current_flag_value = _
if current_flag_name:
if not len(list_of_tokens) == k+1:
if not list_of_tokens[k+1].startswith('-'):
continue
input_flag = InputFlag(name=current_flag_name[current_flag_name.rfind('-')+1:],
prefix=cast(Literal['-', '--', '---'],
current_flag_name[:current_flag_name.rfind('-')+1]),
value=current_flag_value)
all_flags = [flag.get_string_entity() for flag in input_flags.get_flags()]
if input_flag.get_string_entity() not in all_flags:
input_flags.add_flag(input_flag)
else:
raise RepeatedInputFlagsException(input_flag)
current_flag_name, current_flag_value = None, None
if any([current_flag_name, current_flag_value]):
raise UnprocessedInputFlagException()
else:
return InputCommand(trigger=command, input_flags=input_flags)
+4 -4
View File
@@ -1,4 +1,4 @@
from .entity import Router
from .exceptions import (UnknownCommandHandlerHasAlreadyBeenCreatedException,
InvalidDescriptionInstanceException,
InvalidCommandInstanceException)
__all__ = ["Router"]
from argenta.router.entity import Router
+21
View File
@@ -0,0 +1,21 @@
from typing import Callable
from argenta.command import Command
from argenta.command.flag.models import InputFlags
class CommandHandler:
def __init__(self, handler: Callable[[], None] | Callable[[InputFlags], None], handled_command: Command):
self._handler = handler
self._handled_command = handled_command
def handling(self, input_flags: InputFlags = None):
if input_flags is not None:
self._handler(input_flags)
else:
self._handler()
def get_handler(self):
return self._handler
def get_handled_command(self):
return self._handled_command
+21
View File
@@ -0,0 +1,21 @@
from argenta.router.command_handler.entity import CommandHandler
class CommandHandlers:
def __init__(self, command_handlers: list[CommandHandler] = None):
self.command_handlers = command_handlers if command_handlers else []
def get_command_handlers(self) -> list[CommandHandler]:
return self.command_handlers
def add_command_handler(self, command_handler: CommandHandler):
self.command_handlers.append(command_handler)
def add_command_handlers(self, *command_handlers: CommandHandler):
self.command_handlers.extend(command_handlers)
def __iter__(self):
return iter(self.command_handlers)
def __next__(self):
return next(iter(self.command_handlers))
+5
View File
@@ -0,0 +1,5 @@
from argenta.router import Router
system_router = Router(title='System points:',
name='System')
+109 -83
View File
@@ -1,32 +1,37 @@
from typing import Callable, Any
from ..router.exceptions import (InvalidCommandInstanceException,
UnknownCommandHandlerHasAlreadyBeenCreatedException,
InvalidDescriptionInstanceException,
RepeatedCommandException)
from inspect import getfullargspec
from argenta.command import Command
from argenta.command.models import InputCommand
from argenta.router.command_handlers.entity import CommandHandlers
from argenta.router.command_handler.entity import CommandHandler
from argenta.command.flag.models import Flag, Flags, InputFlags
from argenta.router.exceptions import (RepeatedFlagNameException,
TooManyTransferredArgsException,
RequiredArgumentNotPassedException,
IncorrectNumberOfHandlerArgsException,
TriggerCannotContainSpacesException)
class Router:
def __init__(self,
title: str = 'Commands group title:',
name: str = 'subordinate'):
title: str = None,
name: str = 'Default'):
self._title = title
self._name = name
self.title = title
self.name = name
self._command_entities: list[dict[str, Callable[[], None] | str]] = []
self.unknown_command_func: Callable[[str], None] | None = None
self._is_main_router: bool = False
self.ignore_command_register: bool = False
self._command_handlers: CommandHandlers = CommandHandlers()
self._ignore_command_register: bool = False
self._not_valid_flag_handler: Callable[[Flag], None] = lambda flag: print(f"Undefined or incorrect input flag: {flag.get_string_entity()}{(' '+flag.get_value()) if flag.get_value() else ''}")
def command(self, command: str, description: str = None) -> Callable[[Any], Any]:
processed_description = Router._validate_description(command, description)
def command(self, command: Command) -> Callable[[Any], Any]:
self._validate_command(command)
def command_decorator(func):
self._command_entities.append({'handler_func': func,
'command': command,
'description': processed_description})
Router._validate_func_args(command, func)
self._command_handlers.add_command_handler(CommandHandler(func, command))
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@@ -34,90 +39,111 @@ class Router:
return command_decorator
def unknown_command(self, func):
if self.unknown_command_func is not None:
raise UnknownCommandHandlerHasAlreadyBeenCreatedException()
self.unknown_command_func: Callable = func
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
def input_command_handler(self, input_command):
for command_entity in self._command_entities:
if input_command.lower() == command_entity['command'].lower():
if self.ignore_command_register:
return command_entity['handler_func']()
def set_invalid_input_flag_handler(self, func):
processed_args = getfullargspec(func).args
if len(processed_args) != 1:
raise IncorrectNumberOfHandlerArgsException()
else:
if input_command == command_entity['command']:
return command_entity['handler_func']()
self._not_valid_flag_handler = func
def unknown_command_handler(self, unknown_command):
self.unknown_command_func(unknown_command)
def input_command_handler(self, input_command: InputCommand):
input_command_name: str = input_command.get_trigger()
input_command_flags: InputFlags = input_command.get_input_flags()
for command_handler in self._command_handlers:
handle_command = command_handler.get_handled_command()
if input_command_name.lower() == handle_command.get_trigger().lower():
self._validate_input_command(input_command_flags, command_handler)
elif handle_command.get_aliases():
if input_command_name.lower() in handle_command.get_aliases():
self._validate_input_command(input_command_flags, command_handler)
def _validate_command(self, command: str):
if not isinstance(command, str):
raise InvalidCommandInstanceException()
if command in self.get_all_commands():
raise RepeatedCommandException()
if self.ignore_command_register:
if command.lower() in [x.lower() for x in self.get_all_commands()]:
raise RepeatedCommandException()
def _validate_input_command(self, input_command_flags: InputFlags, command_handler: CommandHandler):
handle_command = command_handler.get_handled_command()
if handle_command.get_registered_flags().get_flags():
if input_command_flags.get_flags():
if self._validate_input_flags(handle_command, input_command_flags):
command_handler.handling(input_command_flags)
return
else:
command_handler.handling(input_command_flags)
return
else:
if input_command_flags.get_flags():
self._not_valid_flag_handler(input_command_flags[0])
return
else:
command_handler.handling()
return
def _validate_input_flags(self, handle_command: Command, input_flags: InputFlags):
for flag in input_flags:
is_valid = handle_command.validate_input_flag(flag)
if not is_valid:
self._not_valid_flag_handler(flag)
return False
return True
@staticmethod
def _validate_description(command: str, description: str):
if not isinstance(description, str):
if description is None:
description = f'description for "{command}" command'
else:
raise InvalidDescriptionInstanceException()
return description
def _validate_command(command: Command):
command_name: str = command.get_trigger()
if command_name.find(' ') != -1:
raise TriggerCannotContainSpacesException()
flags: Flags = command.get_registered_flags()
if flags:
flags_name: list = [x.get_string_entity().lower() for x in flags]
if len(set(flags_name)) < len(flags_name):
raise RepeatedFlagNameException()
def set_router_as_main(self):
if self.name == 'subordinate':
self.name = 'main'
self._is_main_router = True
@staticmethod
def _validate_func_args(command: Command, func: Callable):
registered_args = command.get_registered_flags()
transferred_args = getfullargspec(func).args
if registered_args.get_flags() and transferred_args:
if len(transferred_args) != 1:
raise TooManyTransferredArgsException()
elif registered_args.get_flags() and not transferred_args:
raise RequiredArgumentNotPassedException()
elif not registered_args.get_flags() and transferred_args:
raise TooManyTransferredArgsException()
def set_ignore_command_register(self, ignore_command_register: bool):
self.ignore_command_register = ignore_command_register
self._ignore_command_register = ignore_command_register
def get_command_entities(self) -> list[dict[str, Callable[[], None] | str]]:
return self._command_entities
def get_triggers(self):
all_triggers: list[str] = []
for command_handler in self._command_handlers:
all_triggers.append(command_handler.get_handled_command().get_trigger())
return all_triggers
def get_aliases(self):
all_aliases: list[str] = []
for command_handler in self._command_handlers:
if command_handler.get_handled_command().get_aliases():
all_aliases.extend(command_handler.get_handled_command().get_aliases())
return all_aliases
def get_command_handlers(self) -> CommandHandlers:
return self._command_handlers
def get_name(self) -> str:
return self.name
return self._name
def get_title(self) -> str:
return self.title
def get_title(self) -> str | None:
return self._title
def get_router_info(self) -> dict:
return {
'title': self.title,
'name': self.name,
'ignore_command_register': self.ignore_command_register,
'attributes': {
'command_entities': self._command_entities,
'unknown_command_func': self.unknown_command_func,
'is_main_router': self._is_main_router
}
}
def get_all_commands(self) -> list[str]:
all_commands: list[str] = []
for command_entity in self._command_entities:
all_commands.append(command_entity['command'])
return all_commands
def set_title(self, title: str):
self._title = title
+13 -8
View File
@@ -1,18 +1,23 @@
class InvalidCommandInstanceException(Exception):
class RepeatedFlagNameException(Exception):
def __str__(self):
return "Invalid Command Instance"
return "Repeated registered_flag name in register command"
class InvalidDescriptionInstanceException(Exception):
class TooManyTransferredArgsException(Exception):
def __str__(self):
return "Invalid Description Instance"
return "Too many transferred arguments"
class UnknownCommandHandlerHasAlreadyBeenCreatedException(Exception):
class RequiredArgumentNotPassedException(Exception):
def __str__(self):
return "Only one unknown command handler can be declared"
return "Required argument not passed"
class RepeatedCommandException(Exception):
class IncorrectNumberOfHandlerArgsException(Exception):
def __str__(self):
return "Commands in handler cannot be repeated"
return "Handler has incorrect number of arguments"
class TriggerCannotContainSpacesException(Exception):
def __str__(self):
return "Command trigger cannot contain spaces"
Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

+9
View File
@@ -0,0 +1,9 @@
from argenta.app import App
from argenta.app.defaults import PredeterminedMessages
app = App(repeat_command_groups=True)
app.add_message_on_startup(PredeterminedMessages.USAGE + '\n\n')
app.start_polling()
+14
View File
@@ -0,0 +1,14 @@
from argenta.app import App
from argenta.command import Command
from argenta.router import Router
router = Router()
@router.command(Command('test'))
def test():
print('test command')
app = App(ignore_command_register=False)
app.include_router(router)
app.set_unknown_command_handler(lambda command: print(f'Unknown command: {command.get_trigger()}'))
app.start_polling()
+35
View File
@@ -0,0 +1,35 @@
from rich.console import Console
from argenta.command import Command
from argenta.command.flag import Flags, InputFlags
from argenta.command.flag.defaults import PredeterminedFlags
from argenta.router import Router
from .handlers_implementation.help_command import help_command
work_router: Router = Router(title='Work points:')
settings_router: Router = Router()
console = Console()
@work_router.command(Command('get', 'Get Help', aliases=['help', 'Get_help']))
def command_help():
help_command()
@work_router.command(Command('start', 'Start Solving',
flags=Flags(PredeterminedFlags.HOST, PredeterminedFlags.PORT),
aliases=['starting']))
def command_start_solving(args: InputFlags):
print(args.get_flag('host'))
@settings_router.command(Command('update', 'Update WordMath'))
def command_update():
print('eeeeeee')
+24
View File
@@ -0,0 +1,24 @@
from mock.mock_app.handlers.routers import work_router, settings_router
from argenta.app import App
from argenta.app.defaults import PredeterminedMessages
from argenta.app.dividing_line import DynamicDividingLine
from argenta.app.autocompleter import AutoCompleter
autocompleter = AutoCompleter('./mock/.hist')
app: App = App(dividing_line=DynamicDividingLine(),
autocompleter=autocompleter)
def main():
app.include_routers(work_router, settings_router)
app.add_message_on_startup(PredeterminedMessages.USAGE)
app.add_message_on_startup(PredeterminedMessages.AUTOCOMPLETE)
app.add_message_on_startup(PredeterminedMessages.HELP)
app.start_polling()
if __name__ == "__main__":
main()
Generated
-442
View File
@@ -1,442 +0,0 @@
# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand.
[[package]]
name = "art"
version = "6.4"
description = "ASCII Art Library For Python"
optional = false
python-versions = ">=3.6"
groups = ["dev"]
files = [
{file = "art-6.4-py3-none-any.whl", hash = "sha256:4e58b6f0a0bb8574efb311eff24bdd28bf889c0c526ccbbb5410c644340a301c"},
{file = "art-6.4.tar.gz", hash = "sha256:417fea674bff8cea7ed058291ad1b81a6032dfce5152f28e629fa4a798a2c14c"},
]
[package.extras]
dev = ["bandit (>=1.5.1)", "coverage (>=4.1)", "pydocstyle (>=3.0.0)", "vulture (>=1.0)"]
[[package]]
name = "certifi"
version = "2025.1.31"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
groups = ["dev"]
files = [
{file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"},
{file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"},
]
[[package]]
name = "charset-normalizer"
version = "3.4.1"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"},
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"},
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"},
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"},
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"},
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"},
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"},
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"},
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"},
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"},
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"},
{file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"},
{file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"},
{file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"},
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"},
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"},
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"},
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"},
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"},
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"},
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"},
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"},
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"},
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"},
{file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"},
{file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"},
{file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"},
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"},
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"},
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"},
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"},
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"},
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"},
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"},
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"},
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"},
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"},
{file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"},
{file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"},
{file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"},
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"},
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"},
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"},
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"},
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"},
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"},
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"},
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"},
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"},
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"},
{file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"},
{file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"},
{file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"},
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"},
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"},
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"},
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"},
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"},
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"},
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"},
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"},
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"},
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"},
{file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"},
{file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"},
{file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"},
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"},
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"},
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"},
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"},
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"},
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"},
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"},
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"},
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"},
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"},
{file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"},
{file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"},
{file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"},
{file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"},
]
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["dev"]
markers = "platform_system == \"Windows\""
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "idna"
version = "3.10"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.6"
groups = ["dev"]
files = [
{file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
{file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
]
[package.extras]
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
[[package]]
name = "markdown-it-py"
version = "3.0.0"
description = "Python port of markdown-it. Markdown parsing, done right!"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
{file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
]
[package.dependencies]
mdurl = ">=0.1,<1.0"
[package.extras]
benchmarking = ["psutil", "pytest", "pytest-benchmark"]
code-style = ["pre-commit (>=3.0,<4.0)"]
compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"]
linkify = ["linkify-it-py (>=1,<3)"]
plugins = ["mdit-py-plugins"]
profiling = ["gprof2dot"]
rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
[[package]]
name = "mdurl"
version = "0.1.2"
description = "Markdown URL utilities"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
]
[[package]]
name = "numexpr"
version = "2.10.2"
description = "Fast numerical expression evaluator for NumPy"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "numexpr-2.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5b0e82d2109c1d9e63fcd5ea177d80a11b881157ab61178ddbdebd4c561ea46"},
{file = "numexpr-2.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3fc2b8035a0c2cdc352e58c3875cb668836018065cbf5752cb531015d9a568d8"},
{file = "numexpr-2.10.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0db5ff5183935d1612653559c319922143e8fa3019007696571b13135f216458"},
{file = "numexpr-2.10.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15f59655458056fdb3a621b1bb8e071581ccf7e823916c7568bb7c9a3e393025"},
{file = "numexpr-2.10.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ce8cccf944339051e44a49a124a06287fe3066d0acbff33d1aa5aee10a96abb7"},
{file = "numexpr-2.10.2-cp310-cp310-win32.whl", hash = "sha256:ba85371c9a8d03e115f4dfb6d25dfbce05387002b9bc85016af939a1da9624f0"},
{file = "numexpr-2.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:deb64235af9eeba59fcefa67e82fa80cfc0662e1b0aa373b7118a28da124d51d"},
{file = "numexpr-2.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6b360eb8d392483410fe6a3d5a7144afa298c9a0aa3e9fe193e89590b47dd477"},
{file = "numexpr-2.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9a42f5c24880350d88933c4efee91b857c378aaea7e8b86221fff569069841e"},
{file = "numexpr-2.10.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83fcb11988b57cc25b028a36d285287d706d1f536ebf2662ea30bd990e0de8b9"},
{file = "numexpr-2.10.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4213a92efa9770bc28e3792134e27c7e5c7e97068bdfb8ba395baebbd12f991b"},
{file = "numexpr-2.10.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebdbef5763ca057eea0c2b5698e4439d084a0505d9d6e94f4804f26e8890c45e"},
{file = "numexpr-2.10.2-cp311-cp311-win32.whl", hash = "sha256:3bf01ec502d89944e49e9c1b5cc7c7085be8ca2eb9dd46a0eafd218afbdbd5f5"},
{file = "numexpr-2.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:e2d0ae24b0728e4bc3f1d3f33310340d67321d36d6043f7ce26897f4f1042db0"},
{file = "numexpr-2.10.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5323a46e75832334f1af86da1ef6ff0add00fbacdd266250be872b438bdf2be"},
{file = "numexpr-2.10.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a42963bd4c62d8afa4f51e7974debfa39a048383f653544ab54f50a2f7ec6c42"},
{file = "numexpr-2.10.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5191ba8f2975cb9703afc04ae845a929e193498c0e8bcd408ecb147b35978470"},
{file = "numexpr-2.10.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97298b14f0105a794bea06fd9fbc5c423bd3ff4d88cbc618860b83eb7a436ad6"},
{file = "numexpr-2.10.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9d7805ccb6be2d3b0f7f6fad3707a09ac537811e8e9964f4074d28cb35543db"},
{file = "numexpr-2.10.2-cp312-cp312-win32.whl", hash = "sha256:cb845b2d4f9f8ef0eb1c9884f2b64780a85d3b5ae4eeb26ae2b0019f489cd35e"},
{file = "numexpr-2.10.2-cp312-cp312-win_amd64.whl", hash = "sha256:57b59cbb5dcce4edf09cd6ce0b57ff60312479930099ca8d944c2fac896a1ead"},
{file = "numexpr-2.10.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a37d6a51ec328c561b2ca8a2bef07025642eca995b8553a5267d0018c732976d"},
{file = "numexpr-2.10.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:81d1dde7dd6166d8ff5727bb46ab42a6b0048db0e97ceb84a121334a404a800f"},
{file = "numexpr-2.10.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b3f814437d5a10797f8d89d2037cca2c9d9fa578520fc911f894edafed6ea3e"},
{file = "numexpr-2.10.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9309f2e43fe6e4560699ef5c27d7a848b3ff38549b6b57194207cf0e88900527"},
{file = "numexpr-2.10.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ebb73b93f5c4d6994f357fa5a47a9f7a5485577e633b3c46a603cb01445bbb19"},
{file = "numexpr-2.10.2-cp313-cp313-win32.whl", hash = "sha256:ec04c9a3c050c175348801e27c18c68d28673b7bfb865ef88ce333be523bbc01"},
{file = "numexpr-2.10.2-cp313-cp313-win_amd64.whl", hash = "sha256:d7a3fc83c959288544db3adc70612475d8ad53a66c69198105c74036182d10dd"},
{file = "numexpr-2.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0495f8111c3633e265248709b8b3b521bbfa646ba384909edd10e2b9a588a83a"},
{file = "numexpr-2.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2aa05ac71bee3b1253e73173c4d7fa96a09a18970c0226f1c2c07a71ffe988dc"},
{file = "numexpr-2.10.2-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3a23c3002ab330056fbdd2785871937a6f2f2fa85d06c8d0ff74ea8418119d1"},
{file = "numexpr-2.10.2-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a018a7d81326f4c73d8b5aee61794d7d8514512f43957c0db61eb2a8a86848c7"},
{file = "numexpr-2.10.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:037859b17a0abe2b489d4c2cfdadd2bf458ec80dd83f338ea5544c7987e06b85"},
{file = "numexpr-2.10.2-cp39-cp39-win32.whl", hash = "sha256:eb278ccda6f893a312aa0452701bb17d098b7b14eb7c9381517d509cce0a39a3"},
{file = "numexpr-2.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:734b64c6d6a597601ce9d0ef7b666e678ec015b446f1d1412c23903c021436c3"},
{file = "numexpr-2.10.2.tar.gz", hash = "sha256:b0aff6b48ebc99d2f54f27b5f73a58cb92fde650aeff1b397c71c8788b4fff1a"},
]
[package.dependencies]
numpy = ">=1.23.0"
[[package]]
name = "numpy"
version = "2.2.2"
description = "Fundamental package for array computing in Python"
optional = false
python-versions = ">=3.10"
groups = ["dev"]
files = [
{file = "numpy-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7079129b64cb78bdc8d611d1fd7e8002c0a2565da6a47c4df8062349fee90e3e"},
{file = "numpy-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ec6c689c61df613b783aeb21f945c4cbe6c51c28cb70aae8430577ab39f163e"},
{file = "numpy-2.2.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:40c7ff5da22cd391944a28c6a9c638a5eef77fcf71d6e3a79e1d9d9e82752715"},
{file = "numpy-2.2.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:995f9e8181723852ca458e22de5d9b7d3ba4da3f11cc1cb113f093b271d7965a"},
{file = "numpy-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b78ea78450fd96a498f50ee096f69c75379af5138f7881a51355ab0e11286c97"},
{file = "numpy-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fbe72d347fbc59f94124125e73fc4976a06927ebc503ec5afbfb35f193cd957"},
{file = "numpy-2.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8e6da5cffbbe571f93588f562ed130ea63ee206d12851b60819512dd3e1ba50d"},
{file = "numpy-2.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:09d6a2032faf25e8d0cadde7fd6145118ac55d2740132c1d845f98721b5ebcfd"},
{file = "numpy-2.2.2-cp310-cp310-win32.whl", hash = "sha256:159ff6ee4c4a36a23fe01b7c3d07bd8c14cc433d9720f977fcd52c13c0098160"},
{file = "numpy-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:64bd6e1762cd7f0986a740fee4dff927b9ec2c5e4d9a28d056eb17d332158014"},
{file = "numpy-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:642199e98af1bd2b6aeb8ecf726972d238c9877b0f6e8221ee5ab945ec8a2189"},
{file = "numpy-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6d9fc9d812c81e6168b6d405bf00b8d6739a7f72ef22a9214c4241e0dc70b323"},
{file = "numpy-2.2.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c7d1fd447e33ee20c1f33f2c8e6634211124a9aabde3c617687d8b739aa69eac"},
{file = "numpy-2.2.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:451e854cfae0febe723077bd0cf0a4302a5d84ff25f0bfece8f29206c7bed02e"},
{file = "numpy-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd249bc894af67cbd8bad2c22e7cbcd46cf87ddfca1f1289d1e7e54868cc785c"},
{file = "numpy-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02935e2c3c0c6cbe9c7955a8efa8908dd4221d7755644c59d1bba28b94fd334f"},
{file = "numpy-2.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a972cec723e0563aa0823ee2ab1df0cb196ed0778f173b381c871a03719d4826"},
{file = "numpy-2.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6d6a0910c3b4368d89dde073e630882cdb266755565155bc33520283b2d9df8"},
{file = "numpy-2.2.2-cp311-cp311-win32.whl", hash = "sha256:860fd59990c37c3ef913c3ae390b3929d005243acca1a86facb0773e2d8d9e50"},
{file = "numpy-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:da1eeb460ecce8d5b8608826595c777728cdf28ce7b5a5a8c8ac8d949beadcf2"},
{file = "numpy-2.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ac9bea18d6d58a995fac1b2cb4488e17eceeac413af014b1dd26170b766d8467"},
{file = "numpy-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23ae9f0c2d889b7b2d88a3791f6c09e2ef827c2446f1c4a3e3e76328ee4afd9a"},
{file = "numpy-2.2.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3074634ea4d6df66be04f6728ee1d173cfded75d002c75fac79503a880bf3825"},
{file = "numpy-2.2.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8ec0636d3f7d68520afc6ac2dc4b8341ddb725039de042faf0e311599f54eb37"},
{file = "numpy-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ffbb1acd69fdf8e89dd60ef6182ca90a743620957afb7066385a7bbe88dc748"},
{file = "numpy-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0349b025e15ea9d05c3d63f9657707a4e1d471128a3b1d876c095f328f8ff7f0"},
{file = "numpy-2.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:463247edcee4a5537841d5350bc87fe8e92d7dd0e8c71c995d2c6eecb8208278"},
{file = "numpy-2.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9dd47ff0cb2a656ad69c38da850df3454da88ee9a6fde0ba79acceee0e79daba"},
{file = "numpy-2.2.2-cp312-cp312-win32.whl", hash = "sha256:4525b88c11906d5ab1b0ec1f290996c0020dd318af8b49acaa46f198b1ffc283"},
{file = "numpy-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:5acea83b801e98541619af398cc0109ff48016955cc0818f478ee9ef1c5c3dcb"},
{file = "numpy-2.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b208cfd4f5fe34e1535c08983a1a6803fdbc7a1e86cf13dd0c61de0b51a0aadc"},
{file = "numpy-2.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d0bbe7dd86dca64854f4b6ce2ea5c60b51e36dfd597300057cf473d3615f2369"},
{file = "numpy-2.2.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:22ea3bb552ade325530e72a0c557cdf2dea8914d3a5e1fecf58fa5dbcc6f43cd"},
{file = "numpy-2.2.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:128c41c085cab8a85dc29e66ed88c05613dccf6bc28b3866cd16050a2f5448be"},
{file = "numpy-2.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:250c16b277e3b809ac20d1f590716597481061b514223c7badb7a0f9993c7f84"},
{file = "numpy-2.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0c8854b09bc4de7b041148d8550d3bd712b5c21ff6a8ed308085f190235d7ff"},
{file = "numpy-2.2.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b6fb9c32a91ec32a689ec6410def76443e3c750e7cfc3fb2206b985ffb2b85f0"},
{file = "numpy-2.2.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:57b4012e04cc12b78590a334907e01b3a85efb2107df2b8733ff1ed05fce71de"},
{file = "numpy-2.2.2-cp313-cp313-win32.whl", hash = "sha256:4dbd80e453bd34bd003b16bd802fac70ad76bd463f81f0c518d1245b1c55e3d9"},
{file = "numpy-2.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:5a8c863ceacae696aff37d1fd636121f1a512117652e5dfb86031c8d84836369"},
{file = "numpy-2.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b3482cb7b3325faa5f6bc179649406058253d91ceda359c104dac0ad320e1391"},
{file = "numpy-2.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9491100aba630910489c1d0158034e1c9a6546f0b1340f716d522dc103788e39"},
{file = "numpy-2.2.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:41184c416143defa34cc8eb9d070b0a5ba4f13a0fa96a709e20584638254b317"},
{file = "numpy-2.2.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7dca87ca328f5ea7dafc907c5ec100d187911f94825f8700caac0b3f4c384b49"},
{file = "numpy-2.2.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bc61b307655d1a7f9f4b043628b9f2b721e80839914ede634e3d485913e1fb2"},
{file = "numpy-2.2.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fad446ad0bc886855ddf5909cbf8cb5d0faa637aaa6277fb4b19ade134ab3c7"},
{file = "numpy-2.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:149d1113ac15005652e8d0d3f6fd599360e1a708a4f98e43c9c77834a28238cb"},
{file = "numpy-2.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:106397dbbb1896f99e044efc90360d098b3335060375c26aa89c0d8a97c5f648"},
{file = "numpy-2.2.2-cp313-cp313t-win32.whl", hash = "sha256:0eec19f8af947a61e968d5429f0bd92fec46d92b0008d0a6685b40d6adf8a4f4"},
{file = "numpy-2.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:97b974d3ba0fb4612b77ed35d7627490e8e3dff56ab41454d9e8b23448940576"},
{file = "numpy-2.2.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b0531f0b0e07643eb089df4c509d30d72c9ef40defa53e41363eca8a8cc61495"},
{file = "numpy-2.2.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:e9e82dcb3f2ebbc8cb5ce1102d5f1c5ed236bf8a11730fb45ba82e2841ec21df"},
{file = "numpy-2.2.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0d4142eb40ca6f94539e4db929410f2a46052a0fe7a2c1c59f6179c39938d2a"},
{file = "numpy-2.2.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:356ca982c188acbfa6af0d694284d8cf20e95b1c3d0aefa8929376fea9146f60"},
{file = "numpy-2.2.2.tar.gz", hash = "sha256:ed6906f61834d687738d25988ae117683705636936cc605be0bb208b23df4d8f"},
]
[[package]]
name = "pygments"
version = "2.19.1"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"},
{file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"},
]
[package.extras]
windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "requests"
version = "2.32.3"
description = "Python HTTP for Humans."
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
]
[package.dependencies]
certifi = ">=2017.4.17"
charset-normalizer = ">=2,<4"
idna = ">=2.5,<4"
urllib3 = ">=1.21.1,<3"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "rich"
version = "13.9.4"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
optional = false
python-versions = ">=3.8.0"
groups = ["dev"]
files = [
{file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"},
{file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"},
]
[package.dependencies]
markdown-it-py = ">=2.2.0"
pygments = ">=2.13.0,<3.0.0"
[package.extras]
jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "setuptools"
version = "75.8.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3"},
{file = "setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6"},
]
[package.extras]
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"]
core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"]
cover = ["pytest-cov"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
enabler = ["pytest-enabler (>=2.2)"]
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"]
[[package]]
name = "tqdm"
version = "4.67.1"
description = "Fast, Extensible Progress Meter"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"},
{file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"},
]
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[package.extras]
dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"]
discord = ["requests"]
notebook = ["ipywidgets (>=6)"]
slack = ["slack-sdk"]
telegram = ["requests"]
[[package]]
name = "urllib3"
version = "2.3.0"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"},
{file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"},
]
[package.extras]
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "word2number"
version = "1.1"
description = "Convert number words eg. three hundred and forty two to numbers (342)."
optional = false
python-versions = "*"
groups = ["dev"]
files = [
{file = "word2number-1.1.zip", hash = "sha256:70e27a5d387f67b04c71fbb7621c05930b19bfd26efd6851e6e0f9969dcde7d0"},
]
[metadata]
lock-version = "2.1"
python-versions = ">=3.11"
content-hash = "6d71ae12bf4fc44dab312c9626aff93bbc4f5928bf439f144c07de5d9c93d43f"
+14 -11
View File
@@ -1,14 +1,24 @@
[project]
name = "argenta"
version = "0.2.1"
description = "python library for creating cli apps"
version = "0.5.0"
description = "Python library for creating TUI"
authors = [
{name = "kolo", email = "kolo.is.main@gmail.com"}
]
license = {text = "MIT"}
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
dependencies = ["rich (>=14.0.0,<15.0.0)", "art (>=6.4,<7.0)"]
[tool.ruff]
exclude = [
".idea",
"venv",
".git",
"poetry.lock",
".__pycache__",
"tests"
]
@@ -17,12 +27,5 @@ requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.group.dev.dependencies]
art = "^6.4"
rich = "^13.9.4"
numpy = "^2.2.2"
word2number = "^1.1"
numexpr = "^2.10.2"
requests = "^2.32.3"
tqdm = "^4.67.1"
setuptools = "^75.8.0"
pyreadline3 = "^3.5.4"
-23
View File
@@ -1,23 +0,0 @@
from setuptools import setup, find_packages
with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
setup(
name="argenta",
version="0.2.1",
author="kolo",
author_email="kolo.is.main@gmail.com",
description="Python library for creating CLI apps",
long_description=long_description,
long_description_content_type="text/markdown",
packages=find_packages(),
install_requires=[
"requests",
],
classifiers=[
"Programming Language :: Python :: 3",
"Operating System :: OS Independent",
],
python_requires='>=3.11',
)
@@ -1,75 +0,0 @@
import os
import shutil
import time
from zipfile import ZipFile
import requests
from ..local_data_func.get_script_release_tag import get_script_tag
from tqdm import tqdm
class UpdateScript:
GITHUB_REPO = "https://api.github.com/repos/koloideal/WordMath"
@staticmethod
def get_latest_release() -> dict:
response = requests.get(f"{UpdateScript.GITHUB_REPO}/releases/latest")
data = response.json()
return {'tag': data['tag_name'],
'url': data['zipball_url']}
@staticmethod
def download_new_release(zip_url):
response = requests.get(zip_url, stream=True)
response.raise_for_status()
total_size = int(response.headers.get("content-length", 0))
block_size = 1024
with tqdm(total=total_size, unit="B", unit_scale=True) as progress_bar:
with open('new_release.zip', "wb") as file:
for data in response.iter_content(block_size):
progress_bar.update(len(data))
file.write(data)
time.sleep(0.3)
with ZipFile('new_release.zip') as zip_file:
zip_file.extractall("new_release")
@staticmethod
def upgrade_script():
excluded_files = ['venv', '.venv', '.git', 'new_release']
for obj in os.listdir():
if obj not in excluded_files:
if os.path.isfile(obj):
os.remove(obj)
elif os.path.isdir(obj):
shutil.rmtree(obj)
new_release = os.listdir('new_release')
new_release_name = new_release[0] if new_release[0].startswith('koloideal-WordMath') else None
path = f'new_release/{new_release_name}'
all_files = os.listdir(path)
for file in all_files:
shutil.move(path + '/' + file, './' + file)
shutil.rmtree('new_release')
@staticmethod
def start_update() -> bool:
existing_release_tag: str = get_script_tag()
latest_release: dict = UpdateScript.get_latest_release()
latest_release_tag = latest_release['tag']
latest_release_url = latest_release['url']
if latest_release_tag != existing_release_tag:
#UpdateScript.download_new_release(latest_release_url)
#UpdateScript.upgrade_script()
return latest_release_tag
else:
return False
@@ -1,68 +0,0 @@
from numpy import ndarray
from word2number import w2n
from ..local_data_func.get_operator_synonyms import get_operator_synonyms
import numexpr
def word2num_math(string: str) -> ndarray | ZeroDivisionError | OverflowError:
operator_synonyms: dict[str, list[str]] = get_operator_synonyms()
def variables_to_operator(synonym):
for key in operator_synonyms.keys():
if synonym in operator_synonyms[key]:
return key
action = {
"plus": '+',
"minus": '-',
"divide": '/',
"multiply": '*',
"degree": '**',
}
result_string: str = ''
all_variables_of_operators: list[str] = [x for l in operator_synonyms.values() for x in l]
operators = {}
while True:
is_clear = True
number_of_operator = 1
for word in string.split():
try:
if word in all_variables_of_operators:
ope_index = string.index(word)
if ope_index in operators.keys():
is_clear = True
break
else:
raise ValueError
except ValueError:
continue
else:
is_clear = False
operators[number_of_operator] = variables_to_operator(word)
number_of_operator += 1
string = string.replace(word, "&&", 1)
if is_clear:
break
num_of_ope = 1
for number in string.split("&&"):
try:
int_num = w2n.word_to_num(number.strip())
except ValueError:
return ValueError("Invalid input expression")
if num_of_ope in operators.keys():
result_string += f'{int_num} {action[operators[num_of_ope]]} '
num_of_ope += 1
else:
result_string += f'{int_num}'
try:
result = numexpr.evaluate(result_string)
except ZeroDivisionError:
return ZeroDivisionError('Except divide by zero')
except OverflowError:
return OverflowError('Too big result')
else:
return result
@@ -1,20 +0,0 @@
from rich.console import Console
from ...business_logic.word2num_math import word2num_math
console = Console()
print_line_separator = lambda: console.print('\n[bold blue]--------------------------------------[/bold blue]\n')
def start_solving_command():
while True:
console.print(
"\n[italic]Enter a string expression or [bold italic green] Q [/bold italic green] for exit:[/italic]")
string_expression = input()
if string_expression.lower() == 'q':
break
else:
print_line_separator()
console.print(
f'[bold green]Answer:[/bold green] [bold blue]{word2num_math(string_expression)}[/bold blue]')
print_line_separator()
@@ -1,26 +0,0 @@
import requests
from rich.console import Console
from ...business_logic.script_updater import UpdateScript
console = Console()
print_line_separator = lambda: console.print('\n[bold green]--------------------------------------[/bold green]\n')
def upgrade_command():
try:
requests.get('https://ya.ru')
except requests.exceptions.ConnectionError:
console.print('[bold red]No internet connection[/bold red]')
else:
latest_tag = UpdateScript.start_update()
if latest_tag:
print_line_separator()
console.print(f"[bold yellow]The newest version ({latest_tag}) of the script has been successfully installed![/bold yellow]")
print_line_separator()
console.print("[bold yellow]Rerun the script for the changes to take effect[/bold yellow]")
print_line_separator()
exit(0)
else:
console.print('[bold red]You have the latest version installed[/bold red]')
-32
View File
@@ -1,32 +0,0 @@
from rich.console import Console
from argenta.router import Router
from ..handlers.handlers_implementation.help_command import help_command
from ..handlers.handlers_implementation.solving_command import start_solving_command
from ..handlers.handlers_implementation.upgrade_command import upgrade_command
work_router: Router = Router(title='Work points:')
settings_router: Router = Router(title='Settings points:')
console = Console()
@work_router.command(command='0', description='Get Help')
def command_help():
help_command()
@work_router.command(command='1', description='Start Solving')
def command_start_solving():
start_solving_command()
@settings_router.command(command='U', description='Update WordMath')
def command_update():
upgrade_command()
@work_router.unknown_command
def command_unknown_command(command):
console.print(f'[bold red]Unknown command: [/bold red]{command}')
@@ -1,44 +0,0 @@
{
"minus": [
"without",
"lacking",
"deprived_of",
"bereft_of",
"destitute_of",
"minus"
],
"plus": [
"added_to",
"add",
"coupled_with",
"with_the_addition_of",
"plus"
],
"divide": [
"separate",
"part",
"split",
"divide",
"divide_by"
],
"multiply": [
"extend",
"expand",
"spread",
"build_up",
"accumulate",
"augment",
"multiply",
"times"
],
"degree": [
"stage",
"extent",
"grade",
"proportion",
"gradation",
"to_the_power_of",
"degree",
"to_the"
]
}
@@ -1,3 +0,0 @@
{
"tag": "v4.1.1"
}
@@ -1,8 +0,0 @@
import json
def get_operator_synonyms() -> dict[str, list[str]]:
with open("tests/mock_app/local_data/operator_synonyms.json", "r", encoding="utf-8") as file:
operator_synonyms: dict = json.load(file)
return operator_synonyms
@@ -1,8 +0,0 @@
import json
def get_script_tag() -> str:
with open("tests/mock_app/local_data/script_release_tag.json", "r", encoding="utf-8") as file:
script_release_tag: str = json.load(file)['tag']
return script_release_tag
-32
View File
@@ -1,32 +0,0 @@
from tests.mock_app.handlers.routers import work_router, settings_router
from argenta.app.entity import App
from art import text2art
from rich.console import Console
app: App = App(prompt='[italic white bold]What do you want to do(enter number of action)?',
line_separate='[bold green]\n---------------------------------------------\n',
print_func=Console().print,
command_group_description_separate='',
repeat_command_groups=False)
def main():
ascii_name: str = text2art('WordMath', font='nancyj')
initial_greeting: str = f'[bold red]\n\n{ascii_name}'
ascii_goodbye_message: str = text2art('GoodBye', font='small')
goodbye_message: str = f'[bold red]\n{ascii_goodbye_message}{' '*12}made by kolo\n'
app.include_router(work_router, is_main=True)
app.include_router(settings_router)
app.set_initial_greeting(initial_greeting)
app.set_farewell_message(goodbye_message)
app.set_description_message_pattern('[bold red][{command}][/bold red] [blue]*=*=*[/blue] [bold yellow italic]{description}')
app.start_polling()
if __name__ == "__main__":
main()
@@ -1,29 +0,0 @@
from pprint import pprint
from rich.console import Console
from argenta.router import Router
work_router: Router = Router(name='work')
settings_router: Router = Router(name='settings')
console = Console()
@work_router.command(command='a')
def command_help():
console.print('[bold red]command help [/bold red]')
@work_router.command(command='B', description='tester')
def command_start_solving():
console.print('[bold red]command start [/bold red]')
@settings_router.command(command='b')
def command_settings():
console.print('[bold red]command settings [/bold red]')
@work_router.unknown_command
def command_unknown_command(command):
console.print(f'[bold red]Unknown command: [/bold red]{command}')
-18
View File
@@ -1,18 +0,0 @@
from pprint import pprint
from tests.mock_default_app.handlers.routers import work_router, settings_router
from argenta.app.entity import App
from art import text2art
app: App = App(ignore_command_register=False,
line_separate='\n-------------------------------\n')
def main():
app.include_router(work_router, is_main=True)
app.include_router(settings_router)
app.start_polling()
if __name__ == "__main__":
main()
View File
@@ -0,0 +1,216 @@
import _io
from unittest.mock import patch, MagicMock
import unittest
import io
import re
from argenta.app import App
from argenta.command import Command
from argenta.router import Router
from argenta.command.flag.models import Flags, InputFlags
from argenta.command.flag.defaults import PredeterminedFlags
class TestSystemHandlerNormalWork(unittest.TestCase):
@patch("builtins.input", side_effect=["help", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_incorrect_command(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
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.get_trigger()}'))
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn("\nUnknown command: help\n", output)
@patch("builtins.input", side_effect=["TeSt", "Q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_incorrect_command2(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print('test command')
app = App(ignore_command_register=False,
override_system_messages=True,
print_func=print)
app.include_router(router)
app.set_unknown_command_handler(lambda command: print(f'Unknown command: {command.get_trigger()}'))
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\nUnknown command: TeSt\n', output)
@patch("builtins.input", side_effect=["test --help", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command_with_unregistered_flag(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print(f'test command')
app = App(override_system_messages=True,
print_func=print)
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\nUndefined or incorrect input flag: --help\n', output)
@patch("builtins.input", side_effect=["test --port 22", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command_with_unregistered_flag2(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print('test command')
app = App(override_system_messages=True,
print_func=print)
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\nUndefined or incorrect input flag: --port 22\n', output)
@patch("builtins.input", side_effect=["test --host 192.168.32.1 --port 132", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command_with_one_correct_flag_an_one_incorrect_flag(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
flags = Flags(PredeterminedFlags.HOST)
@router.command(Command('test', flags=flags))
def test(args: InputFlags):
print(f'connecting to host {args.get_flag('host').get_value()}')
app = App(override_system_messages=True,
print_func=print)
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\nUndefined or incorrect input flag: --port 132\n', output)
@patch("builtins.input", side_effect=["test", "some", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_one_correct_command_and_one_incorrect_command(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print(f'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.get_trigger()}'))
app.start_polling()
output = mock_stdout.getvalue()
self.assertRegex(output, re.compile(r'\ntest command\n(.|\n)*\nUnknown command: some'))
@patch("builtins.input", side_effect=["test", "some", "more", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_two_correct_commands_and_one_incorrect_command(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print(f'test command')
@router.command(Command('more'))
def test():
print(f'more 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.get_trigger()}'))
app.start_polling()
output = mock_stdout.getvalue()
self.assertRegex(output, re.compile(r'\ntest command\n(.|\n)*\nUnknown command: some\n(.|\n)*\nmore command'))
@patch("builtins.input", side_effect=["test 535 --port", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command_with_incorrect_flag(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print(f'test command')
app = App(override_system_messages=True,
print_func=print)
app.include_router(router)
app.set_invalid_input_flags_handler(lambda command: print(f'Incorrect flag syntax: "{command}"'))
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn("\nIncorrect flag syntax: \"test 535 --port\"\n", output)
@patch("builtins.input", side_effect=["", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_empty_command(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print(f'test command')
app = App(override_system_messages=True,
print_func=print)
app.include_router(router)
app.set_empty_command_handler(lambda: print('Empty input command'))
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn("\nEmpty input command\n", output)
@patch("builtins.input", side_effect=["test --port 22 --port 33", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command_with_repeated_flags(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test', flags=PredeterminedFlags.PORT))
def test(args: InputFlags):
print('test command')
app = App(override_system_messages=True,
print_func=print)
app.include_router(router)
app.set_repeated_input_flags_handler(lambda command: print(f'Repeated input flags: "{command}"'))
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn("\nRepeated input flags: \"test --port 22 --port 33\"\n", output)
@@ -0,0 +1,222 @@
import _io
from unittest.mock import patch, MagicMock
import unittest
import io
import re
from argenta.app import App
from argenta.command.models import Command
from argenta.router import Router
from argenta.command.flag.models import Flag, Flags, InputFlags
from argenta.command.flag.defaults import PredeterminedFlags
class TestSystemHandlerNormalWork(unittest.TestCase):
@patch("builtins.input", side_effect=["test", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print('test command')
app = App(override_system_messages=True,
print_func=print)
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\ntest command\n', output)
@patch("builtins.input", side_effect=["TeSt", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command2(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print('test command')
app = App(ignore_command_register=True,
override_system_messages=True,
print_func=print)
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\ntest command\n', output)
@patch("builtins.input", side_effect=["test --help", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command_with_custom_flag(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
flag = Flag('help', '--', False)
@router.command(Command('test', flags=flag))
def test(args: InputFlags):
print(f'\nhelp for {args.get_flag('help').get_name()} flag\n')
app = App(override_system_messages=True,
print_func=print)
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\nhelp for help flag\n', output)
@patch("builtins.input", side_effect=["test --port 22", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command_with_custom_flag2(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
flag = Flag('port', '--', re.compile(r'^\d{1,5}$'))
@router.command(Command('test', flags=flag))
def test(args: InputFlags):
print(f'flag value for {args.get_flag('port').get_name()} flag : {args.get_flag('port').get_value()}')
app = App(override_system_messages=True,
print_func=print)
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\nflag value for port flag : 22\n', output)
@patch("builtins.input", side_effect=["test -h", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command_with_default_flag(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
flag = PredeterminedFlags.SHORT_HELP
@router.command(Command('test', flags=flag))
def test(args: InputFlags):
print(f'help for {args.get_flag('h').get_name()} flag')
app = App(override_system_messages=True,
print_func=print)
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\nhelp for h flag\n', output)
@patch("builtins.input", side_effect=["test --info", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command_with_default_flag2(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
flag = PredeterminedFlags.INFO
@router.command(Command('test', flags=flag))
def test(args: InputFlags):
if args.get_flag('info'):
print('info about test command')
app = App(override_system_messages=True,
print_func=print)
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\ninfo about test command\n', output)
@patch("builtins.input", side_effect=["test --host 192.168.0.1", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command_with_default_flag3(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
flag = PredeterminedFlags.HOST
@router.command(Command('test', flags=flag))
def test(args: InputFlags):
print(f'connecting to host {args[0].get_value()}')
app = App(override_system_messages=True,
print_func=print)
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\nconnecting to host 192.168.0.1\n', output)
@patch("builtins.input", side_effect=["test --host 192.168.32.1 --port 132", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command_with_two_flags(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
flags = Flags(PredeterminedFlags.HOST, PredeterminedFlags.PORT)
@router.command(Command('test', flags=flags))
def test(args: InputFlags):
print(f'connecting to host {args[0].get_value()} and port {args[1].get_value()}')
app = App(override_system_messages=True,
print_func=print)
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\nconnecting to host 192.168.32.1 and port 132\n', output)
@patch("builtins.input", side_effect=["test", "some", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_two_correct_command(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print(f'test command')
@router.command(Command('some'))
def test2():
print(f'some command')
app = App(override_system_messages=True,
print_func=print)
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertRegex(output, re.compile(r'\ntest command\n(.|\n)*\nsome command\n'))
@patch("builtins.input", side_effect=["test", "some", "more", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_three_correct_command(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print(f'test command')
@router.command(Command('some'))
def test():
print(f'some command')
@router.command(Command('more'))
def test():
print(f'more command')
app = App(override_system_messages=True,
print_func=print)
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertRegex(output, re.compile(r'\ntest command\n(.|\n)*\nsome command\n(.|\n)*\nmore command'))
View File
+24
View File
@@ -0,0 +1,24 @@
from argenta.command.models import InputCommand
from argenta.command.exceptions import (UnprocessedInputFlagException,
RepeatedInputFlagsException,
EmptyInputCommandException)
import unittest
class TestInputCommand(unittest.TestCase):
def test_parse_correct_raw_command(self):
self.assertEqual(InputCommand.parse('ssh --host 192.168.0.3').get_trigger(), 'ssh')
def test_parse_raw_command_without_flag_name_with_value(self):
with self.assertRaises(UnprocessedInputFlagException):
InputCommand.parse('ssh 192.168.0.3')
def test_parse_raw_command_with_repeated_flag_name(self):
with self.assertRaises(RepeatedInputFlagsException):
InputCommand.parse('ssh --host 192.168.0.3 --host 172.198.0.43')
def test_parse_empty_raw_command(self):
with self.assertRaises(EmptyInputCommandException):
InputCommand.parse('')
+21
View File
@@ -0,0 +1,21 @@
from argenta.app.dividing_line import DynamicDividingLine, StaticDividingLine
import unittest
class TestDividingLine(unittest.TestCase):
def test_get_static_dividing_line_full_line(self):
line = StaticDividingLine('-')
self.assertEqual(line.get_full_line().count('-'), 25)
def test_get_dynamic_dividing_line_full_line(self):
line = DynamicDividingLine()
self.assertEqual(line.get_full_line(20).count('-'), 20)
def test_get_dividing_line_unit_part(self):
line = StaticDividingLine('')
self.assertEqual(line.get_unit_part(), ' ')
def test_get_dividing_line2_unit_part(self):
line = StaticDividingLine('+-0987654321!@#$%^&*()_')
self.assertEqual(line.get_unit_part(), '+')
+91
View File
@@ -0,0 +1,91 @@
from argenta.command.flag.models import Flag, InputFlag
import unittest
import re
class TestFlag(unittest.TestCase):
def test_get_string_entity(self):
self.assertEqual(Flag(name='test').get_string_entity(),
'--test')
def test_get_string_entity2(self):
self.assertEqual(Flag(name='test',
prefix='---').get_string_entity(),
'---test')
def test_get_flag_name(self):
self.assertEqual(Flag(name='test').get_name(),
'test')
def test_get_flag_prefix(self):
self.assertEqual(Flag(name='test').get_prefix(),
'--')
def test_get_flag_prefix2(self):
self.assertEqual(Flag(name='test',
prefix='--').get_prefix(),
'--')
def test_get_flag_value_without_set(self):
self.assertEqual(InputFlag(name='test').get_value(),
None)
def test_get_flag_value_with_set(self):
flag = InputFlag(name='test')
flag.set_value('example')
self.assertEqual(flag.get_value(), 'example')
def test_validate_incorrect_flag_value_with_list_of_possible_flag_values(self):
flag = Flag(name='test', possible_values=['1', '2', '3'])
self.assertEqual(flag.validate_input_flag_value('bad value'), False)
def test_validate_correct_flag_value_with_list_of_possible_flag_values(self):
flag = Flag(name='test', possible_values=['1', '2', '3'])
self.assertEqual(flag.validate_input_flag_value('1'), True)
def test_validate_incorrect_flag_value_with_pattern_of_possible_flag_values(self):
flag = Flag(name='test', possible_values=re.compile(r'192.168.\d+.\d+'))
self.assertEqual(flag.validate_input_flag_value('152.123.9.8'), False)
def test_validate_correct_flag_value_with_pattern_of_possible_flag_values(self):
flag = Flag(name='test', possible_values=re.compile(r'192.168.\d+.\d+'))
self.assertEqual(flag.validate_input_flag_value('192.168.9.8'), True)
def test_validate_correct_empty_flag_value_without_possible_flag_values(self):
flag = Flag(name='test', possible_values=False)
self.assertEqual(flag.validate_input_flag_value(None), True)
def test_validate_correct_empty_flag_value_with_possible_flag_values(self):
flag = Flag(name='test', possible_values=True)
self.assertEqual(flag.validate_input_flag_value(None), True)
def test_validate_incorrect_random_flag_value_without_possible_flag_values(self):
flag = Flag(name='test', possible_values=False)
self.assertEqual(flag.validate_input_flag_value('random value'), False)
def test_validate_correct_random_flag_value_with_possible_flag_values(self):
flag = Flag(name='test', possible_values=True)
self.assertEqual(flag.validate_input_flag_value('random value'), True)
+26
View File
@@ -0,0 +1,26 @@
from argenta.command.flag.models import Flag, Flags
import unittest
class TestFlags(unittest.TestCase):
def test_get_flags(self):
flags = Flags()
list_of_flags = [
Flag('test1'),
Flag('test2'),
Flag('test3'),
]
flags.add_flags(list_of_flags)
self.assertEqual(flags.get_flags(),
list_of_flags)
def test_add_flag(self):
flags = Flags()
flags.add_flag(Flag('test'))
self.assertEqual(len(flags.get_flags()), 1)
def test_add_flags(self):
flags = Flags()
flags.add_flags([Flag('test'), Flag('test2')])
self.assertEqual(len(flags.get_flags()), 2)
+36
View File
@@ -0,0 +1,36 @@
from argenta.router import Router
from argenta.command import Command
from argenta.router.exceptions import TriggerCannotContainSpacesException
import unittest
class TestRouter(unittest.TestCase):
def test_get_router_name(self):
self.assertEqual(Router(name='test name').get_name(), 'test name')
def test_get_router_title(self):
self.assertEqual(Router(title='test title').get_title(), 'test title')
def test_register_command_with_spaces_in_trigger(self):
router = Router()
with self.assertRaises(TriggerCannotContainSpacesException):
@router.command(Command(trigger='command with spaces'))
def test():
return 'correct result'