mirror of
https://github.com/koloideal/Argenta.git
synced 2026-06-10 18:15:28 +03:00
Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d30515c1a2 | |||
| 5a6fc1d8ca | |||
| e5d6ead38e | |||
| b61c151e1c | |||
| a57ce490c1 | |||
| edbd45f0bf | |||
| d59d274bca | |||
| 2bf2144815 | |||
| 971258728c | |||
| 2c9c8da13c | |||
| 404758bd91 | |||
| 8fbf651223 | |||
| 459c16ec87 | |||
| d9c74310c3 | |||
| 5e6cdc342e | |||
| a378163431 | |||
| fd4f2e1570 | |||
| 3f7c577c29 | |||
| b72fcc6a11 | |||
| 158e5eb75a | |||
| 46cb2f70c5 | |||
| ef242a5732 | |||
| f87f102ced | |||
| ddf2a2fb10 | |||
| eac5358ead | |||
| 76c18ddbff | |||
| 4eeb4eb182 | |||
| 905698384a | |||
| 79ccfbb3b1 | |||
| a63c46a78b | |||
| a3a7cbf2e6 | |||
| a9e545f3d8 | |||
| 37b62fd69b | |||
| 0ae86d0b2b | |||
| ebfd5a80b3 | |||
| 250704fc88 | |||
| 87239f1501 | |||
| 48c117dd72 | |||
| 1d782f6213 | |||
| ddf396a644 | |||
| c10af04280 | |||
| 890d863391 | |||
| 29b184b2ed | |||
| 6d331a57c1 | |||
| b0eb1e3e6c | |||
| 2d088caaaf | |||
| b4b7d5442c | |||
| 684760121c | |||
| dfc3c472ce | |||
| 8a3f742636 | |||
| 213525915d | |||
| 6174de3bc0 | |||
| c78609caca | |||
| 3f88c5278e | |||
| 93438f8e45 |
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
.venv
|
.venv
|
||||||
.idea
|
.idea
|
||||||
argenta/router/__pycache__/
|
|
||||||
argenta/app/__pycache__/
|
|
||||||
argenta/__pycache__/
|
|
||||||
dist
|
dist
|
||||||
|
poetry.lock
|
||||||
|
*__pycache__/
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,444 @@
|
|||||||
# Argenta
|
# Argenta
|
||||||
Python library for creating cli apps
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Описание
|
||||||
|
**Argenta** — Python library for creating custom shells
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Установка
|
||||||
|
```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 FlagsGroup, Flag
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
registered_flags = FlagsGroup([
|
||||||
|
Flag(flag_name='host',
|
||||||
|
flag_prefix='--',
|
||||||
|
possible_flag_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: dict):
|
||||||
|
for flag in flags:
|
||||||
|
print(f'Flag name: {flag['name']}\n'
|
||||||
|
f'Flag value: {flag['value']}')
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# *classes* :
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## *class* : : `App`
|
||||||
|
Класс, определяющий поведение и состояние оболочки
|
||||||
|
|
||||||
|
### Конструктор
|
||||||
|
```python
|
||||||
|
App(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',
|
||||||
|
system_points_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)
|
||||||
|
```
|
||||||
|
**Аргументы:**
|
||||||
|
- **name : mean**
|
||||||
|
- `prompt` (`str`): Сообщение перед вводом команды.
|
||||||
|
- `initial_greeting` (`str`): Приветственное сообщение при запуске.
|
||||||
|
- `farewell_message` (`str`): Сообщение при выходе.
|
||||||
|
- `exit_command` (`str`): Команда выхода (по умолчанию `'Q'`).
|
||||||
|
- `exit_command_description` (`str`): Описание команды выхода.
|
||||||
|
- `system_points_title` (`str`): Заголовок перед списком системных команд.
|
||||||
|
- `ignore_exit_command_register` (`bool`): Игнорировать регистр команды выхода.
|
||||||
|
- `ignore_command_register` (`bool`): Игнорировать регистр всех команд.
|
||||||
|
- `line_separate` (`str`): Разделительная строка между командами.
|
||||||
|
- `command_group_description_separate` (`str`): Разделитель между группами команд.
|
||||||
|
- `repeat_command_groups` (`bool`): Повторять описание команд перед вводом.
|
||||||
|
- `print_func` (`Callable[[str], None]`): Функция вывода текста в терминал (по умолчанию `print`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ***methods***
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **.start_polling() -> `None`**
|
||||||
|
|
||||||
|
*method mean* **::** запускает цикл обработки ввода
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **.include_router(router: Router) -> `None`**
|
||||||
|
|
||||||
|
*param* `router: Router` **::** регистрируемый роутер
|
||||||
|
*required* **::** True
|
||||||
|
|
||||||
|
*method mean* **::** регистрирует роутер в оболочке
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **.set_initial_message(message: str) -> `None`**
|
||||||
|
|
||||||
|
*param* `message: str` **::** устанавливаемое приветственное сообщение
|
||||||
|
*required* **::** True
|
||||||
|
*example* **::** `"Hello, I'm a example app"`
|
||||||
|
|
||||||
|
*method mean* **::** устанавливает сообщение, которое будет отображено при запуске программы
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **.set_farewell_message(message: str) -> `None`**
|
||||||
|
|
||||||
|
*param* `message: str` **::** устанавливаемое сообщение при выходе
|
||||||
|
*required* **::** True
|
||||||
|
*example* **::** `"GoodBye !"`
|
||||||
|
|
||||||
|
*method mean* **::** устанавливает сообщение, которое будет отображено при выходе
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **.set_description_message_pattern(pattern: str) -> `None`**
|
||||||
|
|
||||||
|
*param* `pattern: str` **::** паттерн описания команды при её выводе в консоль
|
||||||
|
*required* **::** True
|
||||||
|
*example* **::** `"[{command}] *=*=* {description}"`
|
||||||
|
|
||||||
|
*method mean* **::** устанавливает паттерн описания команд, который будет использован
|
||||||
|
при выводе в консоль
|
||||||
|
|
||||||
|
---
|
||||||
|
<a name="custom_handler"></a>
|
||||||
|
#### **.set_repeated_input_flags_handler(handler: Callable[[str], None]) -> `None`**
|
||||||
|
|
||||||
|
*param* `handler: Callable[[str], None]` **::** функция или лямбда функция, которой будет передано управление при
|
||||||
|
вводе юзером повторяющихся флагов
|
||||||
|
*required* **::** True
|
||||||
|
*example* **::** `lambda raw_command: print_func(f'Repeated input flags: "{raw_command}"')`
|
||||||
|
|
||||||
|
*method mean* **::** устанавливает функцию, которой будет передано управление при
|
||||||
|
вводе юзером повторяющихся флагов
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **.set_invalid_input_flags_handler(self, handler: Callable[[str], None]) -> `None`**
|
||||||
|
|
||||||
|
*param* `handler: Callable[[str], None]` **::** функция или лямбда функция, которой будет передано управление при
|
||||||
|
вводе юзером команды с некорректным синтаксисом флагов
|
||||||
|
*required* **::** True
|
||||||
|
*example* **::** `lambda raw_command: print_func(f'Incorrect flag syntax: "{raw_command}"')`
|
||||||
|
|
||||||
|
*method mean* **::** устанавливает функцию, которой будет передано управление при
|
||||||
|
вводе юзером команды с некорректным синтаксисом флагов
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **.set_unknown_command_handler(self, handler: Callable[[str], None]) -> `None`**
|
||||||
|
|
||||||
|
*param* `handler: Callable[[str], None]` **::** функция или лямбда функция, которой будет передано управление при
|
||||||
|
вводе юзером неизвестной команды
|
||||||
|
*required* **::** True
|
||||||
|
*example* **::** `lambda command: print_func(f"Unknown command: {command.get_string_entity()}")`
|
||||||
|
|
||||||
|
*method mean* **::** устанавливает функцию, которой будет передано управление при
|
||||||
|
вводе юзером неизвестной команды
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **.set_empty_command_handler(self, handler: Callable[[str], None]) -> `None`**
|
||||||
|
|
||||||
|
*param* `handler: Callable[[str], None]` **::** функция или лямбда функция, которой будет передано управление при
|
||||||
|
вводе юзером пустой команды
|
||||||
|
*required* **::** True
|
||||||
|
*example* **::** `lambda: print_func(f'Empty input command')`
|
||||||
|
|
||||||
|
*method mean* **::** устанавливает функцию, которой будет передано управление при
|
||||||
|
вводе юзером пустой команды
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Примечания
|
||||||
|
|
||||||
|
- В устанавливаемом паттерне сообщения описания команды необходимы быть два ключевых слова:
|
||||||
|
`command` и `description`, каждое из которых должно быть заключено в фигурные скобки, после обработки
|
||||||
|
паттерна на места этих ключевых слов будут подставлены соответствующие атрибуты команды, при отсутствии
|
||||||
|
этих двух ключевых слов будет вызвано исключение `InvalidDescriptionMessagePatternException`
|
||||||
|
|
||||||
|
- Команды оболочки не должны повторяться, при значении атрибута `ignore_command_register` равным `True`
|
||||||
|
допускается создание обработчиков для разных регистров одинаковых символов в команде, для примера `u` и `U`,
|
||||||
|
при значении атрибута `ignore_command_register` класса `App` равным `False` тот же пример вызывает исключение
|
||||||
|
`RepeatedCommandInDifferentRoutersException`. Исключение вызывается только при наличии пересекающихся команд
|
||||||
|
у __<u>разных</u>__ роутеров
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Исключения
|
||||||
|
|
||||||
|
- `InvalidRouterInstanceException` — Переданный объект в метод `App().include_router()` не является экземпляром класса `Router`.
|
||||||
|
- `InvalidDescriptionMessagePatternException` — Неправильный формат паттерна описания команд.
|
||||||
|
- `IncorrectNumberOfHandlerArgsException` — У обработчика нестандартного поведения зарегистрировано неверное количество аргументов(в большинстве случаев у него должен быть один аргумент).
|
||||||
|
- `NoRegisteredRoutersException` — Отсутствуют зарегистрированные роутеры.
|
||||||
|
- `NoRegisteredHandlersException` — У роутера нет ни одного обработчика команд.
|
||||||
|
- `RepeatedCommandInDifferentRoutersException` — Одна и та же команда зарегистрирована в разных роутерах.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## *class* :: `Router`
|
||||||
|
Класс, который определяет и конфигурирует обработчики команд
|
||||||
|
|
||||||
|
### Конструктор
|
||||||
|
```python
|
||||||
|
Router(title: str = 'Commands group title:',
|
||||||
|
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* **::** возвращает установленный заголовок группы команд данного роутера
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **.get_all_commands() -> `list[str]`**
|
||||||
|
|
||||||
|
*method mean* **::** возвращает все зарегистрированные команды для данного роутера
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Исключения
|
||||||
|
- `RepeatedCommandException` - Одна и та же команда зарегистрирована в одном роутере
|
||||||
|
- `RepeatedFlagNameException` - Повторяющиеся зарегистрированные флаги в команде
|
||||||
|
- `TooManyTransferredArgsException` - Слишком много зарегистрированных аргументов у обработчика команды
|
||||||
|
- `RequiredArgumentNotPassedException` - Не зарегистрирован обязательный аргумент у обработчика команды(аргумент, через который будут переданы флаги введённой команды)
|
||||||
|
- `IncorrectNumberOfHandlerArgsException` - У обработчика нестандартного поведения зарегистрировано неверное количество аргументов(в большинстве случаев у него должен быть один аргумент)
|
||||||
|
- `TriggerCannotContainSpacesException` - У регистрируемой команды в триггере содержатся пробелы
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## *class* :: `Command`
|
||||||
|
Класс, экземпляр которого определяет строковый триггер хэндлера и конфигурирует его атрибуты
|
||||||
|
|
||||||
|
### Конструктор
|
||||||
|
```python
|
||||||
|
Command(trigger: str,
|
||||||
|
description: str = None,
|
||||||
|
flags: Flag | FlagsGroup = None)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Аргументы:**
|
||||||
|
- **name : mean**
|
||||||
|
- `trigger` (`str`): Строковый триггер
|
||||||
|
- `description` (`str`): Описание команды, которое будет выведено в консоль при запуске оболочки
|
||||||
|
- `flags` (`Flag | FlagsGroup`): Флаги, которые будут обработаны при их наличии во вводе юзера
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **.get_trigger() -> `str`**
|
||||||
|
|
||||||
|
*method mean* **::** возвращает строковый триггер экземпляра
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **.get_description() -> `str`**
|
||||||
|
|
||||||
|
*method mean* **::** возвращает описание команды
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **.get_registered_flags() -> `FlagsGroup | None`**
|
||||||
|
|
||||||
|
*method mean* **::** возвращает зарегистрированные флаги экземпляра
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Исключения
|
||||||
|
- `UnprocessedInputFlagException` - Некорректный синтаксис ввода команды
|
||||||
|
- `RepeatedInputFlagsException` - Повторяющиеся флаги во введённой команде
|
||||||
|
- `EmptyInputCommandException` - Введённая команда является пустой(не содержит символов)
|
||||||
|
|
||||||
|
**Примечание**
|
||||||
|
Все вышеуказанные исключения класса `Command` вызываются в рантайме запущенным экземпляром класса
|
||||||
|
`App`, а также по дефолту обрабатываются, при желании можно задать пользовательские
|
||||||
|
обработчики для этих исключений ([подробнее см.](#custom_handler))
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## *class* :: `Flag`
|
||||||
|
Класс, экземпляры которого в большинстве случаев передаются при создании
|
||||||
|
экземпляра класса `Command` для регистрации допустимого флага при вводе юзером команды
|
||||||
|
|
||||||
|
### Конструктор
|
||||||
|
```python
|
||||||
|
Flag(flag_name: str,
|
||||||
|
flag_prefix: typing.Literal['-', '--', '---'] = '-',
|
||||||
|
ignore_flag_value_register: bool = False,
|
||||||
|
possible_flag_values: list[str] | typing.Pattern[str] = False)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Аргументы:**
|
||||||
|
- **name : mean**
|
||||||
|
- `flag_name` (`str`): Имя флага
|
||||||
|
- `flag_prefix` (`Literal['-', '--', '---']`): Префикс команды, допустимым значением является от одного до трёх минусов
|
||||||
|
- `ignore_flag_value_register` (`bool`): Будет ли игнорироваться регистр значения введённого флага
|
||||||
|
- `possible_flag_values` (`list[str] | Pattern[str]`): Множество допустимых значений флага, может быть задано
|
||||||
|
списком с допустимыми значениями или регулярным выражением (рекомендуется `re.compile(r'example exspression')`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ***methods***
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **.get_string_entity() -> `str`**
|
||||||
|
|
||||||
|
*method mean* **::** возвращает строковое представление флага(префикс + имя)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **.get_flag_name() -> `str`**
|
||||||
|
|
||||||
|
*method mean* **::** возвращает имя флага
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **.get_flag_prefix() -> `str`**
|
||||||
|
|
||||||
|
*method mean* **::** возвращает префикс флага
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## *class* :: `FlagsGroup`
|
||||||
|
Класс, объединяющий список флагов в один объект, используется в качестве
|
||||||
|
передаваемого аргумента `flags` экземпляру класса `Command`, при регистрации
|
||||||
|
хэндлера
|
||||||
|
|
||||||
|
### Конструктор
|
||||||
|
```python
|
||||||
|
FlagsGroup(flags: list[Flag] = None)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Аргументы:**
|
||||||
|
- **name : mean**
|
||||||
|
- `flags` (`list[Flag]`): Список флагов, которые будут объединены в одну группу
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ***methods***
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **.get_flags() -> `list[Flag]`**
|
||||||
|
|
||||||
|
*method mean* **::** возвращает зарегистрированные флаги
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Тесты
|
||||||
|
|
||||||
|
Запуск тестов:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m unittest discover
|
||||||
|
```
|
||||||
|
or
|
||||||
|
```bash
|
||||||
|
python -m unittest discover -v
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
from .router import *
|
|
||||||
from .app import *
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
from .entity import App
|
||||||
|
|||||||
+196
-82
@@ -1,145 +1,259 @@
|
|||||||
|
from pprint import pprint
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
from inspect import getfullargspec
|
||||||
|
import re
|
||||||
|
|
||||||
|
from ..command.entity import Command
|
||||||
from ..router.entity import Router
|
from ..router.entity import Router
|
||||||
|
from ..command.exceptions import (UnprocessedInputFlagException,
|
||||||
|
RepeatedInputFlagsException,
|
||||||
|
EmptyInputCommandException)
|
||||||
from .exceptions import (InvalidRouterInstanceException,
|
from .exceptions import (InvalidRouterInstanceException,
|
||||||
InvalidDescriptionMessagePatternException,
|
InvalidDescriptionMessagePatternException,
|
||||||
OnlyOneMainRouterIsAllowedException,
|
NoRegisteredRoutersException,
|
||||||
MissingMainRouterException,
|
NoRegisteredHandlersException,
|
||||||
MissingHandlersForUnknownCommandsOnMainRouterException,
|
RepeatedCommandInDifferentRoutersException,
|
||||||
HandlerForUnknownCommandsCanOnlyBeDeclaredForMainRouterException)
|
IncorrectNumberOfHandlerArgsException)
|
||||||
|
|
||||||
|
|
||||||
class App:
|
class App:
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
prompt: str = 'Enter a command',
|
prompt: str = 'Enter a command',
|
||||||
exit_command: str = 'q',
|
initial_message: str = '\nHello, I am Argenta\n',
|
||||||
|
farewell_message: str = '\nGoodBye\n',
|
||||||
|
invalid_input_flags_message: str = 'Invalid input flags',
|
||||||
|
exit_command: str = 'Q',
|
||||||
|
exit_command_description: str = 'Exit command',
|
||||||
|
system_points_title: str = 'System points:',
|
||||||
ignore_exit_command_register: bool = True,
|
ignore_exit_command_register: bool = True,
|
||||||
initial_greeting: str = 'Hello',
|
ignore_command_register: bool = False,
|
||||||
goodbye_message: str = 'GoodBye',
|
line_separate: str = '',
|
||||||
line_separate: str = '\n',
|
command_group_description_separate: str = '',
|
||||||
command_group_description_separate: str = '\n',
|
repeat_command_groups: bool = True,
|
||||||
print_func: Callable[[str], None] = print) -> None:
|
print_func: Callable[[str], None] = print) -> None:
|
||||||
self.prompt = prompt
|
self.prompt = prompt
|
||||||
self.print_func = print_func
|
self.print_func = print_func
|
||||||
self.exit_command = exit_command
|
self.exit_command = exit_command
|
||||||
|
self.exit_command_description = exit_command_description
|
||||||
|
self.system_points_title = system_points_title
|
||||||
self.ignore_exit_command_register = ignore_exit_command_register
|
self.ignore_exit_command_register = ignore_exit_command_register
|
||||||
self.goodbye_message = goodbye_message
|
self.farewell_message = farewell_message
|
||||||
self.initial_greeting = initial_greeting
|
self.initial_message = initial_message
|
||||||
|
self.invalid_input_flags_message = invalid_input_flags_message
|
||||||
self.line_separate = line_separate
|
self.line_separate = line_separate
|
||||||
self.command_group_description_separate = command_group_description_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._routers: list[Router] = []
|
||||||
self.registered_commands: list[dict[str, str | list[dict[str, Callable[[], None] | str]] | Router]] = []
|
self._description_message_pattern: str = '[{command}] *=*=* {description}'
|
||||||
self.main_app_router: Router | None = None
|
self._registered_router_entities: list[dict[str, str | list[dict[str, Callable[[], None] | Command]] | Router]] = []
|
||||||
self._description_message_pattern = '[{command}] *=*=* {description}'
|
self._invalid_input_flags_handler: Callable[[str], None] = lambda raw_command: print_func(f'Incorrect flag syntax: "{raw_command}"')
|
||||||
|
self._repeated_input_flags_handler: Callable[[str], None] = lambda raw_command: print_func(f'Repeated input flags: "{raw_command}"')
|
||||||
|
self._empty_input_command_handler: Callable[[], None] = lambda: print_func(f'Empty input command')
|
||||||
|
self._unknown_command_handler: Callable[[Command], None] = lambda command: print_func(f"Unknown command: {command.get_string_entity()}")
|
||||||
|
|
||||||
|
|
||||||
def start_polling(self) -> None:
|
def start_polling(self) -> None:
|
||||||
self.print_func(self.initial_greeting)
|
self._validate_number_of_routers()
|
||||||
self.validate_main_router()
|
self._validate_included_routers()
|
||||||
|
self._validate_all_router_commands()
|
||||||
|
|
||||||
while True:
|
self.print_func(self.initial_message)
|
||||||
self.print_command_group_description()
|
|
||||||
|
if not self.repeat_command_groups:
|
||||||
|
self._print_command_group_description()
|
||||||
self.print_func(self.prompt)
|
self.print_func(self.prompt)
|
||||||
|
|
||||||
command: str = input()
|
while True:
|
||||||
|
if self.repeat_command_groups:
|
||||||
|
self._print_command_group_description()
|
||||||
|
self.print_func(self.prompt)
|
||||||
|
|
||||||
self.checking_command_for_exit_command(command)
|
raw_command: str = input()
|
||||||
self.print_func(self.line_separate)
|
|
||||||
|
|
||||||
is_unknown_command: bool = self.check_is_command_unknown(command)
|
try:
|
||||||
|
input_command: Command = Command.parse_input_command(raw_command=raw_command)
|
||||||
|
except UnprocessedInputFlagException:
|
||||||
|
self.print_func(self.line_separate)
|
||||||
|
self._invalid_input_flags_handler(raw_command)
|
||||||
|
self.print_func(self.line_separate)
|
||||||
|
|
||||||
if is_unknown_command:
|
if not self.repeat_command_groups:
|
||||||
|
self.print_func(self.prompt)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for router in self.routers:
|
except RepeatedInputFlagsException:
|
||||||
router.input_command_handler(command)
|
self.print_func(self.line_separate)
|
||||||
|
self._repeated_input_flags_handler(raw_command)
|
||||||
|
self.print_func(self.line_separate)
|
||||||
|
|
||||||
|
if not self.repeat_command_groups:
|
||||||
|
self.print_func(self.prompt)
|
||||||
|
continue
|
||||||
|
|
||||||
|
except EmptyInputCommandException:
|
||||||
|
self.print_func(self.line_separate)
|
||||||
|
self._empty_input_command_handler()
|
||||||
|
self.print_func(self.line_separate)
|
||||||
|
|
||||||
|
if not self.repeat_command_groups:
|
||||||
|
self.print_func(self.prompt)
|
||||||
|
continue
|
||||||
|
|
||||||
|
self._check_command_for_exit_command(input_command.get_trigger())
|
||||||
|
|
||||||
|
self.print_func(self.line_separate)
|
||||||
|
is_unknown_command: bool = self._check_is_command_unknown(input_command)
|
||||||
|
|
||||||
|
if is_unknown_command:
|
||||||
|
self.print_func(self.line_separate)
|
||||||
|
self.print_func(self.command_group_description_separate)
|
||||||
|
if not self.repeat_command_groups:
|
||||||
|
self.print_func(self.prompt)
|
||||||
|
continue
|
||||||
|
|
||||||
|
for router in self._routers:
|
||||||
|
router.input_command_handler(input_command)
|
||||||
|
|
||||||
self.print_func(self.line_separate)
|
self.print_func(self.line_separate)
|
||||||
self.print_func(self.command_group_description_separate)
|
self.print_func(self.command_group_description_separate)
|
||||||
|
if not self.repeat_command_groups:
|
||||||
|
self.print_func(self.prompt)
|
||||||
|
|
||||||
|
|
||||||
def set_initial_greeting(self, greeting: str) -> None:
|
def set_initial_message(self, message: str) -> None:
|
||||||
self.initial_greeting = greeting
|
self.initial_message: str = message
|
||||||
|
|
||||||
|
|
||||||
def set_goodbye_message(self, message: str) -> None:
|
def set_farewell_message(self, message: str) -> None:
|
||||||
self.goodbye_message = message
|
self.farewell_message: str = message
|
||||||
|
|
||||||
|
|
||||||
def set_description_message_pattern(self, pattern: str) -> None:
|
def set_description_message_pattern(self, pattern: str) -> None:
|
||||||
try:
|
first_check = re.match(r'.*{command}.*', pattern)
|
||||||
pattern.format(command='command',
|
second_check = re.match(r'.*{description}.*', pattern)
|
||||||
description='description')
|
|
||||||
except KeyError:
|
if bool(first_check) and bool(second_check):
|
||||||
|
self._description_message_pattern: str = pattern
|
||||||
|
else:
|
||||||
raise InvalidDescriptionMessagePatternException(pattern)
|
raise InvalidDescriptionMessagePatternException(pattern)
|
||||||
self._description_message_pattern = pattern
|
|
||||||
|
|
||||||
|
|
||||||
def validate_main_router(self):
|
def set_invalid_input_flags_handler(self, handler: Callable[[str], None]) -> None:
|
||||||
if not self.main_app_router:
|
args = getfullargspec(handler).args
|
||||||
raise MissingMainRouterException()
|
if len(args) != 1:
|
||||||
|
raise IncorrectNumberOfHandlerArgsException()
|
||||||
if not self.main_app_router.unknown_command_func:
|
else:
|
||||||
raise MissingHandlersForUnknownCommandsOnMainRouterException()
|
self._invalid_input_flags_handler = handler
|
||||||
|
|
||||||
for router in self.routers:
|
|
||||||
if router.unknown_command_func and self.main_app_router is not router:
|
|
||||||
raise HandlerForUnknownCommandsCanOnlyBeDeclaredForMainRouterException()
|
|
||||||
|
|
||||||
|
|
||||||
def checking_command_for_exit_command(self, command: str):
|
def set_repeated_input_flags_handler(self, handler: Callable[[str], None]) -> None:
|
||||||
|
args = getfullargspec(handler).args
|
||||||
|
if len(args) != 1:
|
||||||
|
raise IncorrectNumberOfHandlerArgsException()
|
||||||
|
else:
|
||||||
|
self._repeated_input_flags_handler = handler
|
||||||
|
|
||||||
|
|
||||||
|
def set_unknown_command_handler(self, handler: Callable[[str], None]) -> None:
|
||||||
|
args = getfullargspec(handler).args
|
||||||
|
if len(args) != 1:
|
||||||
|
raise IncorrectNumberOfHandlerArgsException()
|
||||||
|
else:
|
||||||
|
self._unknown_command_handler = handler
|
||||||
|
|
||||||
|
|
||||||
|
def set_empty_command_handler(self, handler: Callable[[str], None]) -> None:
|
||||||
|
args = getfullargspec(handler).args
|
||||||
|
if len(args) != 1:
|
||||||
|
raise IncorrectNumberOfHandlerArgsException()
|
||||||
|
else:
|
||||||
|
self._empty_input_command_handler = handler
|
||||||
|
|
||||||
|
|
||||||
|
def include_router(self, router: Router) -> None:
|
||||||
|
if not isinstance(router, Router):
|
||||||
|
raise InvalidRouterInstanceException()
|
||||||
|
|
||||||
|
router.set_ignore_command_register(self.ignore_command_register)
|
||||||
|
self._routers.append(router)
|
||||||
|
|
||||||
|
command_entities: list[dict[str, Callable[[], None] | Command]] = router.get_command_entities()
|
||||||
|
self._registered_router_entities.append({'name': router.get_name(),
|
||||||
|
'title': router.get_title(),
|
||||||
|
'entity': router,
|
||||||
|
'commands': command_entities})
|
||||||
|
|
||||||
|
|
||||||
|
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_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 _check_command_for_exit_command(self, command: str):
|
||||||
if command.lower() == self.exit_command.lower():
|
if command.lower() == self.exit_command.lower():
|
||||||
if self.ignore_exit_command_register:
|
if self.ignore_exit_command_register:
|
||||||
self.print_func(self.goodbye_message)
|
self.print_func(self.farewell_message)
|
||||||
exit(0)
|
exit(0)
|
||||||
else:
|
else:
|
||||||
if command == self.exit_command:
|
if command == self.exit_command:
|
||||||
self.print_func(self.goodbye_message)
|
self.print_func(self.farewell_message)
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
|
|
||||||
def check_is_command_unknown(self, command: str):
|
def _check_is_command_unknown(self, command: Command):
|
||||||
registered_commands = self.registered_commands
|
registered_router_entities: list[dict[str, str | list[dict[str, Callable[[], None] | Command]] | Router]] = self._registered_router_entities
|
||||||
for router in registered_commands:
|
for router_entity in registered_router_entities:
|
||||||
for command_entity in router['commands']:
|
for command_entity in router_entity['commands']:
|
||||||
if command_entity['command'].lower() == command.lower():
|
if command_entity['command'].get_trigger().lower() == command.get_trigger().lower():
|
||||||
if router['router'].ignore_command_register:
|
if self.ignore_command_register:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
if command_entity['command'] == command:
|
if command_entity['command'].get_trigger() == command.get_trigger():
|
||||||
return False
|
return False
|
||||||
self.main_app_router.unknown_command_handler(command)
|
self._unknown_command_handler(command)
|
||||||
self.print_func(self.line_separate)
|
|
||||||
self.print_func(self.command_group_description_separate)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def print_command_group_description(self):
|
def _print_command_group_description(self):
|
||||||
for router in self.registered_commands:
|
for router_entity in self._registered_router_entities:
|
||||||
self.print_func(router['name'])
|
self.print_func(router_entity['title'])
|
||||||
for command_entity in router['commands']:
|
for command_entity in router_entity['commands']:
|
||||||
self.print_func(self._description_message_pattern.format(
|
self.print_func(self._description_message_pattern.format(
|
||||||
command=command_entity['command'],
|
command=command_entity['command'].get_trigger(),
|
||||||
description=command_entity['description']
|
description=command_entity['command'].get_description()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.print_func(self.command_group_description_separate)
|
self.print_func(self.command_group_description_separate)
|
||||||
|
|
||||||
|
self.print_func(self.system_points_title)
|
||||||
def include_router(self, router: Router, is_main: bool = False) -> None:
|
self.print_func(self._description_message_pattern.format(
|
||||||
if not isinstance(router, Router):
|
command=self.exit_command,
|
||||||
raise InvalidRouterInstanceException()
|
description=self.exit_command_description
|
||||||
|
)
|
||||||
if is_main:
|
)
|
||||||
if not self.main_app_router:
|
self.print_func(self.command_group_description_separate)
|
||||||
self.main_app_router = router
|
|
||||||
router.set_router_as_main()
|
|
||||||
else:
|
|
||||||
raise OnlyOneMainRouterIsAllowedException(router)
|
|
||||||
|
|
||||||
self.routers.append(router)
|
|
||||||
|
|
||||||
registered_commands: list[dict[str, Callable[[], None] | str]] = router.get_registered_commands()
|
|
||||||
self.registered_commands.append({'name': router.get_name(),
|
|
||||||
'router': router,
|
|
||||||
'commands': registered_commands})
|
|
||||||
|
|
||||||
|
|||||||
+10
-14
@@ -13,27 +13,23 @@ class InvalidDescriptionMessagePatternException(Exception):
|
|||||||
f"Your pattern: {self.pattern}")
|
f"Your pattern: {self.pattern}")
|
||||||
|
|
||||||
|
|
||||||
class OnlyOneMainRouterIsAllowedException(Exception):
|
class NoRegisteredRoutersException(Exception):
|
||||||
def __init__(self, existing_main_router):
|
|
||||||
self.existing_main_router = existing_main_router
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return ("Only One Main Router Allowed\n"
|
return "No Registered Router Found"
|
||||||
f"Existing main router is: {self.existing_main_router}")
|
|
||||||
|
|
||||||
|
|
||||||
class MissingMainRouterException(Exception):
|
class NoRegisteredHandlersException(Exception):
|
||||||
|
def __init__(self, router_name):
|
||||||
|
self.router_name = router_name
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return ("Missing Main Router\n"
|
return f"No Registered Handlers Found For '{self.router_name}'"
|
||||||
"One of the registered routers must be the main one")
|
|
||||||
|
|
||||||
|
|
||||||
class MissingHandlersForUnknownCommandsOnMainRouterException(Exception):
|
class RepeatedCommandInDifferentRoutersException(Exception):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return ("Missing Handlers For Unknown Commands On The Main Router\n"
|
return "Commands in different handlers cannot be repeated"
|
||||||
"The main router must have a declared handler for unknown commands")
|
|
||||||
|
|
||||||
|
|
||||||
class HandlerForUnknownCommandsCanOnlyBeDeclaredForMainRouterException(Exception):
|
class IncorrectNumberOfHandlerArgsException(Exception):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '\nThe handler for unknown commands can only be declared for the main router'
|
return "Incorrect Input Flags Handler has incorrect number of arguments"
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
from .entity import Command
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
from argenta.command.flag.entity import Flag
|
||||||
|
from argenta.command.flag.flags_group import FlagsGroup
|
||||||
|
from .exceptions import (UnprocessedInputFlagException,
|
||||||
|
RepeatedInputFlagsException,
|
||||||
|
EmptyInputCommandException)
|
||||||
|
|
||||||
|
from typing import Generic, TypeVar, cast, Literal
|
||||||
|
|
||||||
|
CommandType = TypeVar('CommandType')
|
||||||
|
|
||||||
|
|
||||||
|
class Command(Generic[CommandType]):
|
||||||
|
def __init__(self, trigger: str,
|
||||||
|
description: str = None,
|
||||||
|
flags: Flag | FlagsGroup = None):
|
||||||
|
self._trigger = trigger
|
||||||
|
self._description = f'description for "{self._trigger}" command' if not description else description
|
||||||
|
self._registered_flags: FlagsGroup | None = flags if isinstance(flags, FlagsGroup) else FlagsGroup([flags]) if isinstance(flags, Flag) else flags
|
||||||
|
|
||||||
|
self._input_flags: FlagsGroup | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_trigger(self) -> str:
|
||||||
|
return self._trigger
|
||||||
|
|
||||||
|
|
||||||
|
def get_description(self) -> str:
|
||||||
|
return self._description
|
||||||
|
|
||||||
|
|
||||||
|
def get_registered_flags(self) -> FlagsGroup | None:
|
||||||
|
return self._registered_flags
|
||||||
|
|
||||||
|
|
||||||
|
def validate_input_flag(self, flag: Flag):
|
||||||
|
registered_flags: FlagsGroup | 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 _set_input_flags(self, input_flags: FlagsGroup):
|
||||||
|
self._input_flags = input_flags
|
||||||
|
|
||||||
|
def get_input_flags(self) -> FlagsGroup:
|
||||||
|
return self._input_flags
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_input_command(raw_command: str) -> 'Command[CommandType]':
|
||||||
|
if not raw_command:
|
||||||
|
raise EmptyInputCommandException()
|
||||||
|
list_of_tokens = raw_command.split()
|
||||||
|
command = list_of_tokens[0]
|
||||||
|
list_of_tokens.pop(0)
|
||||||
|
|
||||||
|
flags: FlagsGroup = FlagsGroup()
|
||||||
|
current_flag_name = None
|
||||||
|
current_flag_value = None
|
||||||
|
for k, _ in enumerate(list_of_tokens):
|
||||||
|
if _.startswith('-'):
|
||||||
|
flag_prefix_last_symbol_index = _.rfind('-')
|
||||||
|
if current_flag_name or len(_) < 2 or len(_[:flag_prefix_last_symbol_index]) > 3:
|
||||||
|
raise UnprocessedInputFlagException()
|
||||||
|
else:
|
||||||
|
current_flag_name = _
|
||||||
|
else:
|
||||||
|
if not current_flag_name:
|
||||||
|
raise UnprocessedInputFlagException()
|
||||||
|
else:
|
||||||
|
current_flag_value = _
|
||||||
|
if current_flag_name:
|
||||||
|
if not len(list_of_tokens) == k+1:
|
||||||
|
if not list_of_tokens[k+1].startswith('-'):
|
||||||
|
continue
|
||||||
|
flag_prefix_last_symbol_index = current_flag_name.rfind('-')
|
||||||
|
flag_prefix = current_flag_name[:flag_prefix_last_symbol_index+1]
|
||||||
|
flag_name = current_flag_name[flag_prefix_last_symbol_index+1:]
|
||||||
|
input_flag = Flag(flag_name=flag_name,
|
||||||
|
flag_prefix=cast(Literal['-', '--', '---'], flag_prefix))
|
||||||
|
input_flag.set_value(current_flag_value)
|
||||||
|
|
||||||
|
all_flags = [x.get_string_entity() for x in flags.get_flags()]
|
||||||
|
if input_flag.get_string_entity() not in all_flags:
|
||||||
|
flags.add_flag(input_flag)
|
||||||
|
else:
|
||||||
|
raise RepeatedInputFlagsException(input_flag)
|
||||||
|
|
||||||
|
current_flag_name = None
|
||||||
|
current_flag_value = None
|
||||||
|
if any([current_flag_name, current_flag_value]):
|
||||||
|
raise UnprocessedInputFlagException()
|
||||||
|
if len(flags.get_flags()) == 0:
|
||||||
|
return Command(trigger=command)
|
||||||
|
else:
|
||||||
|
input_command = Command(trigger=command)
|
||||||
|
input_command._set_input_flags(flags)
|
||||||
|
return input_command
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
from argenta.command.flag.entity import Flag
|
||||||
|
|
||||||
|
|
||||||
|
class UnprocessedInputFlagException(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return "Unprocessed Input Flags"
|
||||||
|
|
||||||
|
|
||||||
|
class RepeatedInputFlagsException(Exception):
|
||||||
|
def __init__(self, flag: Flag):
|
||||||
|
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(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return "Input Command is empty"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
from .entity import Flag
|
||||||
|
from .flags_group.entity import FlagsGroup
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
from argenta.command.flag import Flag
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
help_flag = Flag(flag_name='help', possible_flag_values=False)
|
||||||
|
short_help_flag = Flag(flag_name='h', flag_prefix='-', possible_flag_values=False)
|
||||||
|
|
||||||
|
info_flag = Flag(flag_name='info', possible_flag_values=False)
|
||||||
|
short_info_flag = Flag(flag_name='i', flag_prefix='-', possible_flag_values=False)
|
||||||
|
|
||||||
|
all_flag = Flag(flag_name='all', possible_flag_values=False)
|
||||||
|
short_all_flag = Flag(flag_name='a', flag_prefix='-', possible_flag_values=False)
|
||||||
|
|
||||||
|
host_flag = Flag(flag_name='host', possible_flag_values=re.compile(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$'))
|
||||||
|
short_host_flag = Flag(flag_name='h', flag_prefix='-', possible_flag_values=re.compile(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$'))
|
||||||
|
|
||||||
|
port_flag = Flag(flag_name='port', possible_flag_values=re.compile(r'^\d{1,5}$'))
|
||||||
|
short_port_flag = Flag(flag_name='p', flag_prefix='-', possible_flag_values=re.compile(r'^\d{1,5}$'))
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
from typing import Literal, Pattern
|
||||||
|
|
||||||
|
|
||||||
|
class Flag:
|
||||||
|
def __init__(self, flag_name: str,
|
||||||
|
flag_prefix: Literal['-', '--', '---'] = '--',
|
||||||
|
possible_flag_values: list[str] | Pattern[str] | False = True):
|
||||||
|
self._flag_name = flag_name
|
||||||
|
self._flag_prefix = flag_prefix
|
||||||
|
self.possible_flag_values = possible_flag_values
|
||||||
|
|
||||||
|
self._flag_value = None
|
||||||
|
|
||||||
|
def get_string_entity(self):
|
||||||
|
string_entity: str = self._flag_prefix + self._flag_name
|
||||||
|
return string_entity
|
||||||
|
|
||||||
|
def get_flag_name(self):
|
||||||
|
return self._flag_name
|
||||||
|
|
||||||
|
def get_flag_prefix(self):
|
||||||
|
return self._flag_prefix
|
||||||
|
|
||||||
|
def get_value(self):
|
||||||
|
return self._flag_value
|
||||||
|
|
||||||
|
def set_value(self, value):
|
||||||
|
self._flag_value = value
|
||||||
|
|
||||||
|
def validate_input_flag_value(self, input_flag_value: str | None):
|
||||||
|
if self.possible_flag_values is False:
|
||||||
|
if input_flag_value is None:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
elif isinstance(self.possible_flag_values, Pattern):
|
||||||
|
is_valid = bool(self.possible_flag_values.match(input_flag_value))
|
||||||
|
if bool(is_valid):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
elif isinstance(self.possible_flag_values, list):
|
||||||
|
if input_flag_value in self.possible_flag_values:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
from .entity import FlagsGroup
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
from argenta.command.flag.entity import Flag
|
||||||
|
|
||||||
|
|
||||||
|
class FlagsGroup:
|
||||||
|
def __init__(self, *flags: Flag):
|
||||||
|
self._flags: list[Flag] = [] if not flags else flags
|
||||||
|
|
||||||
|
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 unparse_to_dict(self):
|
||||||
|
result_dict: dict[str, dict] = {}
|
||||||
|
for flag in self._flags:
|
||||||
|
result_dict[flag.get_flag_name()] = {
|
||||||
|
'name': flag.get_flag_name(),
|
||||||
|
'string_entity': flag.get_string_entity(),
|
||||||
|
'prefix': flag.get_flag_prefix(),
|
||||||
|
'value': flag.get_value()
|
||||||
|
}
|
||||||
|
return result_dict
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self._flags)
|
||||||
|
|
||||||
|
def __next__(self):
|
||||||
|
return next(iter(self))
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
return self._flags[item]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
from .entity import Router
|
||||||
+101
-47
@@ -1,70 +1,124 @@
|
|||||||
from typing import Callable, Any
|
from typing import Callable, Any
|
||||||
from ..router.exceptions import (InvalidCommandInstanceException,
|
from inspect import getfullargspec
|
||||||
UnknownCommandHandlerHasAlreadyBeenCreatedException,
|
|
||||||
InvalidDescriptionInstanceException)
|
from ..command.entity import Command
|
||||||
|
from argenta.command.flag.entity import Flag
|
||||||
|
from argenta.command.flag.flags_group import FlagsGroup
|
||||||
|
from ..router.exceptions import (RepeatedCommandException,
|
||||||
|
RepeatedFlagNameException,
|
||||||
|
TooManyTransferredArgsException,
|
||||||
|
RequiredArgumentNotPassedException,
|
||||||
|
IncorrectNumberOfHandlerArgsException,
|
||||||
|
TriggerCannotContainSpacesException)
|
||||||
|
|
||||||
|
|
||||||
class Router:
|
class Router:
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
name: str,
|
title: str = 'Commands group title:',
|
||||||
ignore_command_register: bool = False):
|
name: str = 'Default'):
|
||||||
|
|
||||||
self.ignore_command_register: bool = ignore_command_register
|
self._title = title
|
||||||
self._name = name
|
self._name = name
|
||||||
|
|
||||||
self.processed_commands: list[dict[str, Callable[[], None] | str]] = []
|
self._command_entities: list[dict[str, Callable[[], None] | Command]] = []
|
||||||
self.unknown_command_func: Callable[[str], None] | None = None
|
self._ignore_command_register: bool = False
|
||||||
self._is_main_router: 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()}'")
|
||||||
|
|
||||||
|
|
||||||
def command(self, command: str, description: str) -> Callable[[Any], Any]:
|
def command(self, command: Command) -> Callable[[Any], Any]:
|
||||||
if not isinstance(command, str):
|
self._validate_command(command)
|
||||||
raise InvalidCommandInstanceException()
|
|
||||||
if not isinstance(description, str):
|
def command_decorator(func):
|
||||||
raise InvalidDescriptionInstanceException()
|
Router._validate_func_args(command, func)
|
||||||
|
self._command_entities.append({'handler_func': func,
|
||||||
|
'command': command})
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
return command_decorator
|
||||||
|
|
||||||
|
def set_invalid_input_flag_handler(self, func):
|
||||||
|
processed_args = getfullargspec(func).args
|
||||||
|
if len(processed_args) != 1:
|
||||||
|
raise IncorrectNumberOfHandlerArgsException()
|
||||||
else:
|
else:
|
||||||
def command_decorator(func):
|
self._not_valid_flag_handler = func
|
||||||
self.processed_commands.append({'func': func,
|
|
||||||
'command': command,
|
|
||||||
'description': description})
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
return wrapper
|
|
||||||
return command_decorator
|
|
||||||
|
|
||||||
|
|
||||||
def unknown_command(self, func):
|
def input_command_handler(self, input_command: Command):
|
||||||
if self.unknown_command_func is not None:
|
input_command_name: str = input_command.get_trigger()
|
||||||
raise UnknownCommandHandlerHasAlreadyBeenCreatedException()
|
input_command_flags: FlagsGroup = input_command.get_input_flags()
|
||||||
|
for command_entity in self._command_entities:
|
||||||
self.unknown_command_func = func
|
if input_command_name.lower() == command_entity['command'].get_trigger().lower():
|
||||||
|
if command_entity['command'].get_registered_flags():
|
||||||
def wrapper(*args, **kwargs):
|
if input_command_flags:
|
||||||
return func(*args, **kwargs)
|
for flag in input_command_flags:
|
||||||
return wrapper
|
is_valid = command_entity['command'].validate_input_flag(flag)
|
||||||
|
if not is_valid:
|
||||||
|
self._not_valid_flag_handler(flag)
|
||||||
def input_command_handler(self, input_command):
|
return
|
||||||
for command_entity in self.processed_commands:
|
return command_entity['handler_func'](input_command_flags.unparse_to_dict())
|
||||||
if input_command.lower() == command_entity['command'].lower():
|
else:
|
||||||
if self.ignore_command_register:
|
return command_entity['handler_func']({})
|
||||||
return command_entity['func']()
|
|
||||||
else:
|
else:
|
||||||
if input_command == command_entity['command']:
|
if input_command_flags:
|
||||||
return command_entity['func']()
|
self._not_valid_flag_handler(input_command_flags[0])
|
||||||
|
return
|
||||||
def unknown_command_handler(self, unknown_command):
|
else:
|
||||||
self.unknown_command_func(unknown_command)
|
return command_entity['handler_func']()
|
||||||
|
|
||||||
|
|
||||||
def set_router_as_main(self):
|
def _validate_command(self, command: Command):
|
||||||
self._is_main_router = True
|
command_name: str = command.get_trigger()
|
||||||
|
if command_name.find(' ') != -1:
|
||||||
|
raise TriggerCannotContainSpacesException()
|
||||||
|
if command_name in self.get_all_commands():
|
||||||
|
raise RepeatedCommandException()
|
||||||
|
if self._ignore_command_register:
|
||||||
|
if command_name.lower() in [x.lower() for x in self.get_all_commands()]:
|
||||||
|
raise RepeatedCommandException()
|
||||||
|
|
||||||
|
flags: FlagsGroup = 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 get_registered_commands(self) -> list[dict[str, Callable[[], None] | str]]:
|
@staticmethod
|
||||||
return self.processed_commands
|
def _validate_func_args(command: Command, func: Callable):
|
||||||
|
registered_args = command.get_registered_flags()
|
||||||
|
transferred_args = getfullargspec(func).args
|
||||||
|
if registered_args and transferred_args:
|
||||||
|
if len(transferred_args) != 1:
|
||||||
|
raise TooManyTransferredArgsException()
|
||||||
|
elif registered_args and not transferred_args:
|
||||||
|
raise RequiredArgumentNotPassedException()
|
||||||
|
elif not registered_args and transferred_args:
|
||||||
|
raise TooManyTransferredArgsException()
|
||||||
|
|
||||||
|
|
||||||
|
def set_ignore_command_register(self, ignore_command_register: bool):
|
||||||
|
self._ignore_command_register = ignore_command_register
|
||||||
|
|
||||||
|
|
||||||
|
def get_command_entities(self) -> list[dict[str, Callable[[], None] | Command]]:
|
||||||
|
return self._command_entities
|
||||||
|
|
||||||
|
|
||||||
def get_name(self) -> str:
|
def get_name(self) -> str:
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
|
|
||||||
|
def get_title(self) -> str:
|
||||||
|
return self._title
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_commands(self) -> list[str]:
|
||||||
|
all_commands: list[str] = []
|
||||||
|
for command_entity in self._command_entities:
|
||||||
|
all_commands.append(command_entity['command'].get_trigger())
|
||||||
|
|
||||||
|
return all_commands
|
||||||
|
|||||||
@@ -1,13 +1,28 @@
|
|||||||
class InvalidCommandInstanceException(Exception):
|
class RepeatedCommandException(Exception):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Invalid Command Instance"
|
return "Commands in handler cannot be repeated"
|
||||||
|
|
||||||
|
|
||||||
class InvalidDescriptionInstanceException(Exception):
|
class RepeatedFlagNameException(Exception):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Invalid Description Instance"
|
return "Repeated flag name in register command"
|
||||||
|
|
||||||
|
|
||||||
class UnknownCommandHandlerHasAlreadyBeenCreatedException(Exception):
|
class TooManyTransferredArgsException(Exception):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Only one unknown command handler can be declared"
|
return "Too many transferred arguments"
|
||||||
|
|
||||||
|
|
||||||
|
class RequiredArgumentNotPassedException(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return "Required argument not passed"
|
||||||
|
|
||||||
|
|
||||||
|
class IncorrectNumberOfHandlerArgsException(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return "Handler has incorrect number of arguments"
|
||||||
|
|
||||||
|
|
||||||
|
class TriggerCannotContainSpacesException(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return "Command trigger cannot contain spaces"
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from typing import cast, Literal
|
||||||
|
|
||||||
|
print(cast(Literal['-', '--', '---'], '----'))
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
from rich.console import Console
|
||||||
|
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
|
def help_command():
|
||||||
|
console.print("[italic bold]The main functionality of the script is to convert an expression from a string "
|
||||||
|
"to a mathematical one and then calculate this expression. "
|
||||||
|
"Project GitHub: https://github.com/koloideal/WordMath[/italic bold]")
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import re
|
||||||
|
from pprint import pprint
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
|
from argenta.command import Command
|
||||||
|
from argenta.command.flag import Flag, FlagsGroup
|
||||||
|
from argenta.command.flag.defaults import host_flag, port_flag
|
||||||
|
from argenta.router import Router
|
||||||
|
from .handlers_implementation.help_command import help_command
|
||||||
|
|
||||||
|
|
||||||
|
work_router: Router = Router(title='Work nts:')
|
||||||
|
work_router.set_invalid_input_flag_handler(lambda flag: print(f'Invalid input flag: "{flag.get_string_entity()} {flag.get_value()}"'))
|
||||||
|
|
||||||
|
settings_router: Router = Router(title='Settings points:')
|
||||||
|
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
|
@work_router.command(Command(trigger='0', description='Get Help'))
|
||||||
|
def command_help():
|
||||||
|
help_command()
|
||||||
|
|
||||||
|
|
||||||
|
@work_router.command(Command(trigger='j', description='Start Solving', flags=FlagsGroup(host_flag, port_flag)))
|
||||||
|
def command_start_solving(args: dict):
|
||||||
|
print('Solving...')
|
||||||
|
pprint(args)
|
||||||
|
#start_solving_command()
|
||||||
|
|
||||||
|
|
||||||
|
@settings_router.command(Command(trigger='G', description='Update WordMath'))
|
||||||
|
def command_update():
|
||||||
|
print('Command update')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
from mock.mock_app.handlers.routers import work_router, settings_router
|
||||||
|
from art import text2art
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
|
from argenta.app import App
|
||||||
|
|
||||||
|
app: App = App(prompt='[italic white bold]What do you want to do(enter number of action)?',
|
||||||
|
line_separate=f'\n{"[bold green]-[/bold green][bold red]-[/bold red]"*25}\n',
|
||||||
|
print_func=Console().print,
|
||||||
|
command_group_description_separate='',
|
||||||
|
repeat_command_groups=True,
|
||||||
|
ignore_exit_command_register=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)
|
||||||
|
app.include_router(settings_router)
|
||||||
|
|
||||||
|
app.set_initial_message(initial_greeting)
|
||||||
|
app.set_farewell_message(goodbye_message)
|
||||||
|
|
||||||
|
app.set_invalid_input_flags_handler(lambda raw_command: print(f"Invalid input flags: {raw_command}"))
|
||||||
|
app.set_unknown_command_handler(lambda command: print(f"Unknown command: {command.get_trigger()}"))
|
||||||
|
app.set_repeated_input_flags_handler(lambda raw_command: print(f"Repeated input flags: {raw_command}"))
|
||||||
|
|
||||||
|
app.set_description_message_pattern('[bold red][{command}][/bold red] [blue]*=*=*[/blue] [bold yellow italic]{description}')
|
||||||
|
|
||||||
|
app.start_polling()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Generated
-7
@@ -1,7 +0,0 @@
|
|||||||
# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand.
|
|
||||||
package = []
|
|
||||||
|
|
||||||
[metadata]
|
|
||||||
lock-version = "2.1"
|
|
||||||
python-versions = ">=3.11"
|
|
||||||
content-hash = "f5666f5625d676c506924a57dc0520a1f3ed2b2c774baed3dc85353594f8473d"
|
|
||||||
+25
-5
@@ -1,17 +1,37 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "argenta"
|
name = "argenta"
|
||||||
version = "0.1.1"
|
version = "0.3.9"
|
||||||
description = "python library for creating cli apps"
|
description = "python library for creating custom shells"
|
||||||
authors = [
|
authors = [
|
||||||
{name = "kolo",email = "kolo.is.main@gmail.com"}
|
{name = "kolo", email = "kolo.is.main@gmail.com"}
|
||||||
]
|
]
|
||||||
license = {text = "MIT"}
|
license = {text = "MIT"}
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [] # no dependencies
|
||||||
]
|
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
exclude = [
|
||||||
|
".idea",
|
||||||
|
"venv",
|
||||||
|
".git",
|
||||||
|
"poetry.lock",
|
||||||
|
".__pycache__",
|
||||||
|
"tests"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
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"
|
||||||
|
pyreadline3 = "^3.5.4"
|
||||||
|
|
||||||
|
|||||||
@@ -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.1.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,5 +0,0 @@
|
|||||||
from argenta.app.entity import *
|
|
||||||
|
|
||||||
|
|
||||||
def test():
|
|
||||||
assert App().exit_command == 'q'
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
from argenta.app import App
|
||||||
|
from argenta.app.exceptions import (InvalidDescriptionMessagePatternException,
|
||||||
|
NoRegisteredRoutersException)
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class TestApp(unittest.TestCase):
|
||||||
|
def test_set_invalid_description_message_pattern(self):
|
||||||
|
with self.assertRaises(InvalidDescriptionMessagePatternException):
|
||||||
|
App().set_description_message_pattern('Invalid description pattern')
|
||||||
|
|
||||||
|
def test_set_invalid_description_message_pattern2(self):
|
||||||
|
with self.assertRaises(InvalidDescriptionMessagePatternException):
|
||||||
|
App().set_description_message_pattern('Invalid {desription} description {comand} pattern')
|
||||||
|
|
||||||
|
def test_no_registered_router(self):
|
||||||
|
with self.assertRaises(NoRegisteredRoutersException):
|
||||||
|
App()._validate_number_of_routers()
|
||||||
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
from argenta.command import Command
|
||||||
|
from argenta.command.exceptions import (UnprocessedInputFlagException,
|
||||||
|
RepeatedInputFlagsException,
|
||||||
|
EmptyInputCommandException)
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class TestCommand(unittest.TestCase):
|
||||||
|
def test_parse_correct_raw_command(self):
|
||||||
|
self.assertEqual(Command.parse_input_command('ssh --host 192.168.0.3').get_trigger(), 'ssh')
|
||||||
|
|
||||||
|
def test_parse_raw_command_without_flag_name_with_value(self):
|
||||||
|
with self.assertRaises(UnprocessedInputFlagException):
|
||||||
|
Command.parse_input_command('ssh 192.168.0.3')
|
||||||
|
|
||||||
|
def test_parse_raw_command_with_repeated_flag_name(self):
|
||||||
|
with self.assertRaises(RepeatedInputFlagsException):
|
||||||
|
Command.parse_input_command('ssh --host 192.168.0.3 --host 172.198.0.43')
|
||||||
|
|
||||||
|
def test_parse_empty_raw_command(self):
|
||||||
|
with self.assertRaises(EmptyInputCommandException):
|
||||||
|
Command.parse_input_command('')
|
||||||
|
|
||||||
|
def test_get_command_description(self):
|
||||||
|
self.assertEqual(Command(trigger='test', description='test description').get_description(), 'test description')
|
||||||
|
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
from argenta.command.flag import Flag
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class TestFlag(unittest.TestCase):
|
||||||
|
def test_get_string_entity(self):
|
||||||
|
self.assertEqual(Flag(flag_name='test').get_string_entity(),
|
||||||
|
'--test')
|
||||||
|
|
||||||
|
def test_get_string_entity2(self):
|
||||||
|
self.assertEqual(Flag(flag_name='test',
|
||||||
|
flag_prefix='---').get_string_entity(),
|
||||||
|
'---test')
|
||||||
|
|
||||||
|
def test_get_flag_name(self):
|
||||||
|
self.assertEqual(Flag(flag_name='test').get_flag_name(),
|
||||||
|
'test')
|
||||||
|
|
||||||
|
def test_get_flag_prefix(self):
|
||||||
|
self.assertEqual(Flag(flag_name='test').get_flag_prefix(),
|
||||||
|
'--')
|
||||||
|
|
||||||
|
def test_get_flag_prefix2(self):
|
||||||
|
self.assertEqual(Flag(flag_name='test',
|
||||||
|
flag_prefix='--').get_flag_prefix(),
|
||||||
|
'--')
|
||||||
|
|
||||||
|
def test_get_flag_value_without_set(self):
|
||||||
|
self.assertEqual(Flag(flag_name='test').get_value(),
|
||||||
|
None)
|
||||||
|
|
||||||
|
def test_get_flag_value_with_set(self):
|
||||||
|
flag = Flag(flag_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(flag_name='test', possible_flag_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(flag_name='test', possible_flag_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(flag_name='test', possible_flag_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(flag_name='test', possible_flag_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(flag_name='test', possible_flag_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(flag_name='test', possible_flag_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(flag_name='test', possible_flag_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(flag_name='test', possible_flag_values=True)
|
||||||
|
self.assertEqual(flag.validate_input_flag_value('random value'), True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
from argenta.command.flag import Flag, FlagsGroup
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class TestFlagsGroup(unittest.TestCase):
|
||||||
|
def test_get_flags(self):
|
||||||
|
flags = FlagsGroup()
|
||||||
|
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 = FlagsGroup()
|
||||||
|
flags.add_flag(Flag('test'))
|
||||||
|
self.assertEqual(len(flags.get_flags()), 1)
|
||||||
|
|
||||||
|
def test_add_flags(self):
|
||||||
|
flags = FlagsGroup()
|
||||||
|
flags.add_flags([Flag('test'), Flag('test2')])
|
||||||
|
self.assertEqual(len(flags.get_flags()), 2)
|
||||||
|
|
||||||
|
def test_unparse_flags_to_dict(self):
|
||||||
|
list_of_flags = [
|
||||||
|
Flag('test1'),
|
||||||
|
Flag('test2'),
|
||||||
|
Flag('test3'),
|
||||||
|
]
|
||||||
|
flags = FlagsGroup(*list_of_flags)
|
||||||
|
serialized_flags = flags.unparse_to_dict()
|
||||||
|
needed_result = {'test1': {'name': 'test1',
|
||||||
|
'prefix': '--',
|
||||||
|
'string_entity': '--test1',
|
||||||
|
'value': None},
|
||||||
|
'test2': {'name': 'test2',
|
||||||
|
'prefix': '--',
|
||||||
|
'string_entity': '--test2',
|
||||||
|
'value': None},
|
||||||
|
'test3': {'name': 'test3',
|
||||||
|
'prefix': '--',
|
||||||
|
'string_entity': '--test3',
|
||||||
|
'value': None}}
|
||||||
|
|
||||||
|
self.assertDictEqual(serialized_flags, needed_result)
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
from argenta.command.flag import FlagsGroup, Flag
|
||||||
|
from argenta.router import Router
|
||||||
|
from argenta.command import Command
|
||||||
|
from argenta.router.exceptions import RepeatedCommandException, 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_input_correct_command(self):
|
||||||
|
router = Router()
|
||||||
|
@router.command(Command(trigger='test'))
|
||||||
|
def test():
|
||||||
|
return 'correct result'
|
||||||
|
|
||||||
|
self.assertEqual(router.input_command_handler(Command(trigger='test')), 'correct result')
|
||||||
|
|
||||||
|
def test_input_command_with_invalid_flag(self):
|
||||||
|
router = Router()
|
||||||
|
router.set_invalid_input_flag_handler(lambda x: x)
|
||||||
|
|
||||||
|
@router.command(Command(trigger='test'))
|
||||||
|
def test():
|
||||||
|
return 'correct result'
|
||||||
|
|
||||||
|
input_command = Command(trigger='test')
|
||||||
|
input_command._set_input_flags(FlagsGroup([Flag('host')]))
|
||||||
|
|
||||||
|
self.assertEqual(router.input_command_handler(input_command), None)
|
||||||
|
|
||||||
|
def test_input_correct_command_with_one_register_and_ignore_command_register(self):
|
||||||
|
router = Router()
|
||||||
|
router.set_ignore_command_register(True)
|
||||||
|
@router.command(Command(trigger='test'))
|
||||||
|
def test():
|
||||||
|
return 'correct result'
|
||||||
|
|
||||||
|
self.assertEqual(router.input_command_handler(Command(trigger='test')), 'correct result')
|
||||||
|
|
||||||
|
def test_input_correct_command_with_different_register_and_ignore_command_register(self):
|
||||||
|
router = Router()
|
||||||
|
router.set_ignore_command_register(True)
|
||||||
|
@router.command(Command(trigger='test'))
|
||||||
|
def test():
|
||||||
|
return 'correct result'
|
||||||
|
|
||||||
|
self.assertEqual(router.input_command_handler(Command(trigger='TeSt')), 'correct result')
|
||||||
|
|
||||||
|
def test_input_incorrect_command_with_ignore_command_register(self):
|
||||||
|
router = Router()
|
||||||
|
router.set_ignore_command_register(True)
|
||||||
|
@router.command(Command(trigger='test'))
|
||||||
|
def test():
|
||||||
|
return 'correct result'
|
||||||
|
|
||||||
|
self.assertEqual(router.input_command_handler(Command(trigger='Test2')), None)
|
||||||
|
|
||||||
|
def test_register_repeated_commands_with_one_register(self):
|
||||||
|
router = Router()
|
||||||
|
@router.command(Command(trigger='test'))
|
||||||
|
def test():
|
||||||
|
return 'correct result'
|
||||||
|
|
||||||
|
with self.assertRaises(RepeatedCommandException):
|
||||||
|
@router.command(Command(trigger='test'))
|
||||||
|
def test():
|
||||||
|
return 'correct result'
|
||||||
|
|
||||||
|
def test_register_commands_with_different_register(self):
|
||||||
|
router = Router()
|
||||||
|
@router.command(Command(trigger='test'))
|
||||||
|
def test():
|
||||||
|
return 'correct result'
|
||||||
|
|
||||||
|
try:
|
||||||
|
@router.command(Command(trigger='Test'))
|
||||||
|
def test():
|
||||||
|
return 'correct result'
|
||||||
|
except RepeatedCommandException:
|
||||||
|
self.fail('RepeatedCommandException should not have been thrown')
|
||||||
|
|
||||||
|
def test_register_repeated_commands_with_one_register_and_set_ignore_command_register(self):
|
||||||
|
router = Router()
|
||||||
|
router.set_ignore_command_register(True)
|
||||||
|
@router.command(Command(trigger='test'))
|
||||||
|
def test():
|
||||||
|
return 'correct result'
|
||||||
|
|
||||||
|
with self.assertRaises(RepeatedCommandException):
|
||||||
|
@router.command(Command(trigger='test'))
|
||||||
|
def test():
|
||||||
|
return 'correct result'
|
||||||
|
|
||||||
|
def test_register_repeated_commands_with_different_register_and_set_ignore_command_register(self):
|
||||||
|
router = Router()
|
||||||
|
router.set_ignore_command_register(True)
|
||||||
|
@router.command(Command(trigger='test'))
|
||||||
|
def test():
|
||||||
|
return 'correct result'
|
||||||
|
|
||||||
|
with self.assertRaises(RepeatedCommandException):
|
||||||
|
@router.command(Command(trigger='Test'))
|
||||||
|
def test():
|
||||||
|
return 'correct result'
|
||||||
|
|
||||||
|
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'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user