15 Commits

Author SHA1 Message Date
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
47 changed files with 903 additions and 744 deletions
+263 -92
View File
@@ -3,7 +3,7 @@
---
## Описание
**Argenta** — это библиотека для создания CLI-приложений на Python. Она предоставляет удобные инструменты для маршрутизации команд и обработки пользовательского ввода.
**Argenta** — Python library for creating custom shells
---
@@ -19,21 +19,21 @@ poetry add argenta
---
# Быстрый старт
Пример базового CLI-приложения с Argenta:
Пример простейшей оболочки с командой без зарегистрированных флагов
```python
# routers.py
from argenta.router import Router
from argenta.command import Command
router = Router()
@router.command("hello")
def hello():
@router.command(Command("hello"))
def handler():
print("Hello, world!")
@router.unknown_command
def unlnown_command(command):
print(f'Command "{command}" undefined')
```
```python
# main.py
from argenta.app import App
@@ -49,28 +49,56 @@ def main() -> None:
if __name__ == '__main__':
main()
```
Пример оболочки с командой, у которой зарегистрированы флаги
```python
# routers.py
import re
from argenta.router import Router
from argenta.command import Command
from argenta.command.params.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* :
---
## declared *classes* :
## *class* : : `App`
Класс, определяющий поведение и состояние оболочки
---
### *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',
exit_command_title: str = 'System points:',
system_points_title: str = 'System points:',
ignore_exit_command_register: bool = True,
ignore_command_register: bool = False,
line_separate: str = '',
@@ -85,7 +113,7 @@ App(prompt: str = 'Enter a command',
- `farewell_message` (`str`): Сообщение при выходе.
- `exit_command` (`str`): Команда выхода (по умолчанию `'Q'`).
- `exit_command_description` (`str`): Описание команды выхода.
- `exit_command_title` (`str`): Заголовок перед списком команд выхода.
- `system_points_title` (`str`): Заголовок перед списком системных команд.
- `ignore_exit_command_register` (`bool`): Игнорировать регистр команды выхода.
- `ignore_command_register` (`bool`): Игнорировать регистр всех команд.
- `line_separate` (`str`): Разделительная строка между командами.
@@ -95,179 +123,322 @@ App(prompt: str = 'Enter a command',
---
#### **declared *methods***
### ***methods***
---
**App().**`start_polling() -> None`
#### **.start_polling() -> `None`**
*method mean* **::** запускает жизненный цикл приложения
*method mean* **::** запускает цикл обработки ввода
---
**App().**`include_router(router: Router, is_main: bool = False) -> None`
#### **.include_router(router: Router) -> `None`**
*param* `router: Router` **::** регистрируемый роутер
*required* **::** True
*param* `is_main: bool` **::** будет ли являться регистрируемый роутер главным
*example* **::** `True` или `False`
*method mean* **::** регистрирует роутер в приложении
*method mean* **::** регистрирует роутер в оболочке
---
**App().**`set_initial_message(message: str) -> None`
#### **.set_initial_message(message: str) -> `None`**
*param* `message: str` **::** устанавливаемое приветственное сообщение
*example* **::** `"Hello, I'm a cli example app"`
*required* **::** True
*example* **::** `"Hello, I'm a example app"`
*method mean* **::** устанавливает сообщение, которое будет отображено при запуске программы
---
**App().**`set_farewell_message(message: str) -> None`
#### **.set_farewell_message(message: str) -> `None`**
*param* `message: str` **::** устанавливаемое сообщение при выходе
*required* **::** True
*example* **::** `"GoodBye !"`
*method mean* **::** устанавливает сообщение, которое будет отображено при выходе
---
**App().**`set_description_message_pattern(pattern: str) -> None`
#### **.set_description_message_pattern(pattern: str) -> `None`**
*param* `pattern: str` **::** паттерн описания команды при её выводе в консоль
*required* **::** True
*example* **::** `"[{command}] *=*=* {description}"`
*method mean* **::** устанавливает приветственное сообщение
*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* **::** устанавливает функцию, которой будет передано управление при
вводе юзером повторяющихся флагов
---
**App().**`get_main_router() -> Router`
#### **.set_invalid_input_flags_handler(self, handler: Callable[[str], None]) -> `None`**
*method mean* **::** возвращает `Router()`, который является главным в приложении
*param* `handler: Callable[[str], None]` **::** функция или лямбда функция, которой будет передано управление при
вводе юзером команды с некорректным синтаксисом флагов
*required* **::** True
*example* **::** `lambda raw_command: print_func(f'Incorrect flag syntax: "{raw_command}"')`
*method mean* **::** устанавливает функцию, которой будет передано управление при
вводе юзером команды с некорректным синтаксисом флагов
---
**App().**`get_all_app_commands() -> list[str]`
#### **.set_unknown_command_handler(self, handler: Callable[[str], None]) -> `None`**
*method mean* **::** возвращает список команд всех зарегистрированных роутеров, сохраняя их регистр
*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`**
- Среди зарегистрированных в приложении роутеров должен быть один главный, является ли роутер главным
определяется значением аргумента `is_main` равным `True`, в методе `App().include_router()`, который по умолчанию равен
`False`, если в приложении зарегистрирован лишь один роутер, то он неявно устанавливается главным, если
зарегистрировано больше одного роутера, то требуется явное указание главного. При регистрации более одного
главного роутера вызывается исключение `OnlyOneMainRouterIsAllowedException`. При регистрации более одного
роутера и отсутствии указания главного вызывается исключение `MissingMainRouterException`
*param* `handler: Callable[[str], None]` **::** функция или лямбда функция, которой будет передано управление при
вводе юзером пустой команды
*required* **::** True
*example* **::** `lambda: print_func(f'Empty input command')`
*method mean* **::** устанавливает функцию, которой будет передано управление при
вводе юзером пустой команды
---
### Примечания
- В устанавливаемом паттерне сообщения описания команды необходимы быть два ключевых слова:
`command` и `description`, каждое из которых должно быть заключено в фигурные скобки, после обработки
паттерна на места этих ключевых слов будут подставлены соответствующие значения команды, при отсутствии
паттерна на места этих ключевых слов будут подставлены соответствующие атрибуты команды, при отсутствии
этих двух ключевых слов будет вызвано исключение `InvalidDescriptionMessagePatternException`
- Команды приложения не должны повторяться, при значении атрибута `ignore_command_register` равным `True`
- Команды оболочки не должны повторяться, при значении атрибута `ignore_command_register` равным `True`
допускается создание обработчиков для разных регистров одинаковых символов в команде, для примера `u` и `U`,
при значении атрибута `ignore_command_register` класса `App` равным `False` тот же пример вызывает исключение
`RepeatedCommandInDifferentRoutersException`. Исключение вызывается только при наличии пересекающихся команд
у __<u>разных</u>__ роутеров
- У главного обработчика должен быть зарегистрирован обработчик неизвестных команд:
```python
router = Router()
@router.unknown_command
def unknown_command(command):
print(f'Command "{command}" undefined')
```
При отсутствии обработчика неизвестных команд у главного роутера будет вызвано исключение
`MissingHandlerForUnknownCommandsException`. При регистрации обработчика неизвестных команд у
__<u>не</u>__ главного роутера будет вызвано исключение `HandlerForUnknownCommandsOnNonMainRouterException`
#### Исключения
### Исключения
- `InvalidRouterInstanceException` — Переданный объект в метод `App().include_router()` не является экземпляром класса `Router`.
- `InvalidDescriptionMessagePatternException` — Неправильный формат паттерна описания команд.
- `OnlyOneMainRouterIsAllowedException` — Регистрация более одного главного роутера.
- `MissingMainRouterException` — Отсутствует главный роутер.
- `MissingHandlerForUnknownCommandsException` — В основном роутере отсутствует обработчик неизвестных команд.
- `HandlerForUnknownCommandsOnNonMainRouterException` — Обработчик неизвестных команд определён не у основного роутера.
- `IncorrectNumberOfHandlerArgsException` — У обработчика нестандартного поведения зарегистрировано неверное количество аргументов(в большинстве случаев у него должен быть один аргумент).
- `NoRegisteredRoutersException` — Отсутствуют зарегистрированные роутеры.
- `NoRegisteredHandlersException` — У роутера нет ни одного обработчика команд.
- `RepeatedCommandInDifferentRoutersException` — Одна и та же команда зарегистрирована в разных роутерах.
---
### *class* :: `Router`
## *class* :: `Router`
Класс, который определяет и конфигурирует обработчики команд
#### Конструктор
### Конструктор
```python
Router(title: str = 'Commands group title:',
name: str = 'subordinate')
name: str = 'Default')
```
**Аргументы:**
- **name : mean**
- `title` (`str`): Заголовок группы команд.
- `name` (`str`): Персональное название роутера
---
#### **declared *methods***
### ***methods***
---
**`@`Router().**`command(command: str, description: str = None)`
#### **command(command: Command)**
*param* `command: str` **::** строковый триггер, который будет выполнять указанные действия
*example* **::** `U` / `update` / `ExaMPLE`
*param* `command: Command` **::** экземпляр класса `Command`, который определяет строковый триггер команды,
допустимые флаги команды и другое
*required* **::** True
*example* **::** `Command(command='ssh', description='connect via ssh')`
*param* `description: str` **::** описание команды, которое будет выведено в консоль
*example* **::** `description for update command` или `example description`
*method mean* **::** декоратор регистрирует функцию как обработчик команды
*method mean* **::** декоратор, который регистрирует функцию как обработчик команды
---
**`@`Router().**`unknown_command`
*method mean* **::** декоратор регистрирует функцию как обработчик неизвестных команд
---
**Router().**`get_name() -> str`
#### **.get_name() -> `str`**
*method mean* **::** возвращает установленное название роутера
---
**Router().**`get_title() -> str`
#### **.get_title() -> `str`**
*method mean* **::** возвращает установленный заголовок группы команд данного роутера
---
**Router().**`get_router_info() -> dict`
*method mean* **::** возвращает информацию о роутере
---
**Router().**`get_all_commands() -> list[str]`
#### **.get_all_commands() -> `list[str]`**
*method mean* **::** возвращает все зарегистрированные команды для данного роутера
---
#### Исключения
- `InvalidCommandInstanceException` - Переданный объект для регистрации команды не является строкой
- `InvalidDescriptionInstanceException` - Переданный объект для регистрации описания команды не является строкой
- `UnknownCommandHandlerHasAlreadyBeenCreatedException` - Обработчик неизвестных команд уже создан
### Исключения
- `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
```
-2
View File
@@ -1,3 +1 @@
from .entity import App
from .exceptions import (InvalidDescriptionMessagePatternException,
InvalidRouterInstanceException)
+65 -50
View File
@@ -1,18 +1,18 @@
from typing import Callable
from inspect import getfullargspec
import re
from ..command.entity import Command
from ..router.entity import Router
from ..command.input_comand.entity import InputCommand
from ..command.input_comand.exceptions import (IncorrectInputFlagException,
InvalidInputFlagsHandlerHasBeenAlreadyCreatedException,
IncorrectNumberArgsHandlerException,
UnknownCommandHandlerHasBeenAlreadyCreatedException)
from ..command.exceptions import (UnprocessedInputFlagException,
RepeatedInputFlagsException,
EmptyInputCommandException)
from .exceptions import (InvalidRouterInstanceException,
InvalidDescriptionMessagePatternException,
NoRegisteredRoutersException,
NoRegisteredHandlersException,
RepeatedCommandInDifferentRoutersException)
RepeatedCommandInDifferentRoutersException,
IncorrectNumberOfHandlerArgsException)
class App:
@@ -23,7 +23,7 @@ class App:
invalid_input_flags_message: str = 'Invalid input flags',
exit_command: str = 'Q',
exit_command_description: str = 'Exit command',
exit_command_title: str = 'System points:',
system_points_title: str = 'System points:',
ignore_exit_command_register: bool = True,
ignore_command_register: bool = False,
line_separate: str = '',
@@ -34,7 +34,7 @@ class App:
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.system_points_title = system_points_title
self.ignore_exit_command_register = ignore_exit_command_register
self.farewell_message = farewell_message
self.initial_message = initial_message
@@ -45,11 +45,12 @@ class App:
self.repeat_command_groups = repeat_command_groups
self._routers: list[Router] = []
self._invalid_input_flags_handler: Callable[[str], None] | None = None
self._unknown_command_handler: Callable[[Command], None] | None = None
self._registered_router_entities: list[dict[str, str | list[dict[str, Callable[[], None] | Command]] | Router]] = []
self._app_main_router: Router | None = None
self._description_message_pattern: str = '[{command}] *=*=* {description}'
self._registered_router_entities: list[dict[str, str | list[dict[str, Callable[[], None] | Command]] | Router]] = []
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:
@@ -71,24 +72,42 @@ class App:
raw_command: str = input()
try:
input_command: InputCommand = InputCommand.parse(raw_command=raw_command)
except IncorrectInputFlagException:
input_command: Command = Command.parse_input_command(raw_command=raw_command)
except UnprocessedInputFlagException:
self.print_func(self.line_separate)
if self._invalid_input_flags_handler:
self._invalid_input_flags_handler(raw_command)
else:
self.print_func(f'Incorrect flag syntax: "{raw_command}"')
self.print_func(self.line_separate)
if not self.repeat_command_groups:
self.print_func(self.prompt)
continue
self._checking_command_for_exit_command(input_command.get_string_entity())
except RepeatedInputFlagsException:
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
@@ -111,42 +130,45 @@ class App:
def set_description_message_pattern(self, pattern: str) -> None:
try:
pattern.format(command='command',
description='description')
except KeyError:
raise InvalidDescriptionMessagePatternException(pattern)
first_check = re.match(r'.*{command}.*', pattern)
second_check = re.match(r'.*{description}.*', pattern)
if bool(first_check) and bool(second_check):
self._description_message_pattern: str = pattern
else:
raise InvalidDescriptionMessagePatternException(pattern)
def set_invalid_input_flags_handler(self, handler: Callable[[str], None]) -> None:
if self._invalid_input_flags_handler:
raise InvalidInputFlagsHandlerHasBeenAlreadyCreatedException()
else:
args = getfullargspec(handler).args
if len(args) != 1:
raise IncorrectNumberArgsHandlerException()
raise IncorrectNumberOfHandlerArgsException()
else:
self._invalid_input_flags_handler = handler
def set_unknown_command_handler(self, handler: Callable[[str], None]) -> None:
if self._unknown_command_handler:
raise UnknownCommandHandlerHasBeenAlreadyCreatedException()
else:
def set_repeated_input_flags_handler(self, handler: Callable[[str], None]) -> None:
args = getfullargspec(handler).args
if len(args) != 1:
raise IncorrectNumberArgsHandlerException()
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 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 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:
@@ -190,7 +212,7 @@ class App:
raise RepeatedCommandInDifferentRoutersException()
def _checking_command_for_exit_command(self, command: str):
def _check_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)
@@ -205,20 +227,13 @@ class App:
registered_router_entities: list[dict[str, str | list[dict[str, Callable[[], None] | Command]] | Router]] = self._registered_router_entities
for router_entity in registered_router_entities:
for command_entity in router_entity['commands']:
if command_entity['command'].get_string_entity().lower() == command.get_string_entity().lower():
if command_entity['command'].get_trigger().lower() == command.get_trigger().lower():
if self.ignore_command_register:
return False
else:
if command_entity['command'].get_string_entity() == command.get_string_entity():
if command_entity['command'].get_trigger() == command.get_trigger():
return False
if self._unknown_command_handler:
self._unknown_command_handler(command)
else:
print(f"Unknown command: {command.get_string_entity()}")
self.print_func(self.line_separate)
self.print_func(self.command_group_description_separate)
return True
@@ -227,13 +242,13 @@ class App:
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'].get_string_entity(),
command=command_entity['command'].get_trigger(),
description=command_entity['command'].get_description()
)
)
self.print_func(self.command_group_description_separate)
self.print_func(self.exit_command_title)
self.print_func(self.system_points_title)
self.print_func(self._description_message_pattern.format(
command=self.exit_command,
description=self.exit_command_description
+5
View File
@@ -28,3 +28,8 @@ class NoRegisteredHandlersException(Exception):
class RepeatedCommandInDifferentRoutersException(Exception):
def __str__(self):
return "Commands in different handlers cannot be repeated"
class IncorrectNumberOfHandlerArgsException(Exception):
def __str__(self):
return "Incorrect Input Flags Handler has incorrect number of arguments"
+1
View File
@@ -0,0 +1 @@
from .entity import Command
+80 -32
View File
@@ -1,47 +1,40 @@
from .params.flag.entity import Flag
from .params.flag.flags_group.entity import FlagsGroup
from .exceptions import (InvalidCommandInstanceException,
InvalidDescriptionInstanceException,
InvalidFlagsInstanceException)
from .params.flag.input_flag.entity import InputFlag
from .exceptions import (UnprocessedInputFlagException,
RepeatedInputFlagsException,
EmptyInputCommandException)
from typing import Generic, TypeVar
class Command:
def __init__(self, command: str,
description: str | None = None,
flags: Flag | FlagsGroup | None = None):
self._command = command
self._description = description
self._flags: FlagsGroup | None = flags if isinstance(flags, FlagsGroup) else FlagsGroup([flags]) if isinstance(flags, Flag) else flags
T = TypeVar('T')
self._input_flags: InputFlag | FlagsGroup | None = None
def get_string_entity(self):
return self._command
class Command(Generic[T]):
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
def get_description(self):
if not self._description:
description = f'description for "{self._command}" command'
return description
else:
self._input_flags: FlagsGroup | None = None
def get_trigger(self) -> str:
return self._trigger
def get_description(self) -> str:
return self._description
def get_flags(self):
return self._flags
def set_command(self, command: str):
self._command = command
def get_registered_flags(self) -> FlagsGroup | None:
return self._registered_flags
def validate_commands_params(self):
if not isinstance(self._command, str):
raise InvalidCommandInstanceException(self._command)
if not isinstance(self._description, str):
raise InvalidDescriptionInstanceException()
if not any([(isinstance(self._flags, Flag), isinstance(self._flags, FlagsGroup)), not self._flags]):
raise InvalidFlagsInstanceException
def validate_input_flag(self, flag: InputFlag):
registered_flags: FlagsGroup | Flag | None = self._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():
@@ -57,4 +50,59 @@ class Command:
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[T]':
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 _ in 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 and current_flag_value:
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=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
+12 -6
View File
@@ -1,13 +1,19 @@
class InvalidCommandInstanceException(Exception):
from .params.flag.entity import Flag
class UnprocessedInputFlagException(Exception):
def __str__(self):
return "Invalid Command Instance"
return "Unprocessed Input Flags"
class InvalidDescriptionInstanceException(Exception):
class RepeatedInputFlagsException(Exception):
def __init__(self, flag: Flag):
self.flag = flag
def __str__(self):
return "Invalid Description Instance"
return ("Repeated Input Flags\n"
f"Duplicate flag was detected in the input: '{self.flag.get_string_entity()}'")
class InvalidFlagsInstanceException(Exception):
class EmptyInputCommandException(Exception):
def __str__(self):
return "Invalid Flags Instance"
return "Input Command is empty"
-66
View File
@@ -1,66 +0,0 @@
from ..input_comand.exceptions import IncorrectInputFlagException, RepeatedInputFlagsException
from ..entity import Command
from ..params.flag.flags_group.entity import FlagsGroup
from ..params.flag.input_flag.entity import InputFlag
from typing import Generic, TypeVar
T = TypeVar('T')
class InputCommand(Command, Generic[T]):
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(raw_command: str) -> 'InputCommand[T]':
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 _ in 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 IncorrectInputFlagException()
else:
current_flag_name = _
else:
if not current_flag_name:
raise IncorrectInputFlagException()
else:
current_flag_value = _
if current_flag_name and current_flag_value:
flag_prefix_last_symbol_index = current_flag_name.rfind('-')
flag_prefix = current_flag_name[:flag_prefix_last_symbol_index]
flag_name = current_flag_name[flag_prefix_last_symbol_index:]
input_flag = InputFlag(flag_name=flag_name,
flag_prefix=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 IncorrectInputFlagException()
if len(flags.get_flags()) == 0:
return InputCommand(command=command)
else:
input_command = InputCommand(command=command)
input_command.set_input_flags(flags)
return input_command
@@ -1,39 +0,0 @@
from ..params.flag.input_flag.entity import InputFlag
class InvalidInputFlagException(Exception):
def __init__(self, flag: InputFlag):
self.flag = flag
def __str__(self):
return ("Invalid Input Flags\n"
f"Unknown or invalid input flag: '{self.flag.get_string_entity()} {self.flag.get_value()}'")
class IncorrectInputFlagException(Exception):
def __str__(self):
return "Incorrect Input Flags"
class RepeatedInputFlagsException(Exception):
def __init__(self, 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 InvalidInputFlagsHandlerHasBeenAlreadyCreatedException(Exception):
def __str__(self):
return "Invalid Input Flags Handler has already been created"
class UnknownCommandHandlerHasBeenAlreadyCreatedException(Exception):
def __str__(self):
return "Unknown Command Handler has already been created"
class IncorrectNumberArgsHandlerException(Exception):
def __str__(self):
return "Incorrect Input Flags Handler has incorrect number of arguments"
+2
View File
@@ -0,0 +1,2 @@
from .entity import Flag
from .flags_group.entity import FlagsGroup
+14 -8
View File
@@ -1,17 +1,17 @@
from typing import Literal
from typing import Literal, Pattern
class Flag:
def __init__(self, flag_name: str,
flag_prefix: Literal['-', '--', '---'] = '-',
flag_prefix: Literal['-', '--', '---'] = '--',
ignore_flag_value_register: bool = False,
possible_flag_values: list[str] = False):
possible_flag_values: list[str] | Pattern[str] = False):
self._flag_name = flag_name
self._flag_prefix = flag_prefix
self.possible_flag_values = possible_flag_values
self.ignore_flag_value_register = ignore_flag_value_register
self._value = None
self._flag_value = None
def get_string_entity(self):
string_entity: str = self._flag_prefix + self._flag_name
@@ -24,13 +24,20 @@ class Flag:
return self._flag_prefix
def get_value(self):
return self._value
return self._flag_value
def set_value(self, value):
self._value = value
self._flag_value = value
def validate_input_flag_value(self, input_flag_value: str):
if self.possible_flag_values:
if 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
if isinstance(self.possible_flag_values, list):
if self.ignore_flag_value_register:
if input_flag_value.lower() in [x.lower() for x in self.possible_flag_values]:
return True
@@ -41,5 +48,4 @@ class Flag:
return True
else:
return False
else:
return True
@@ -1,22 +1,35 @@
from argenta.command.params.flag.entity import Flag
from argenta.command.params.flag.input_flag.entity import InputFlag
class FlagsGroup:
def __init__(self, flags: list[Flag | InputFlag] = None):
self._flags: list[Flag | InputFlag] = [] if not flags else flags
def __init__(self, flags: list[Flag] = None):
self._flags: list[Flag] = [] if not flags else flags
def get_flags(self):
def get_flags(self) -> list[Flag]:
return self._flags
def add_flag(self, flag: Flag | InputFlag):
def add_flag(self, flag: Flag):
self._flags.append(flag)
def add_flags(self, flags: list[Flag | InputFlag]):
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]
@@ -1,11 +0,0 @@
from ...flag.entity import Flag
class InputFlag(Flag):
def set_value(self, value: str):
self._value = value
def get_value(self) -> str:
return self._value
-1
View File
@@ -1,2 +1 @@
from .entity import Router
from .exceptions import InvalidDescriptionInstanceException
+40 -48
View File
@@ -1,32 +1,32 @@
from typing import Callable, Any
from inspect import getfullargspec
from ..command.entity import Command
from ..command.input_comand.entity import InputCommand
from ..command.input_comand.exceptions import InvalidInputFlagException
from ..command.params.flag.entity import Flag
from ..command.params.flag.flags_group.entity import FlagsGroup
from ..router.exceptions import (RepeatedCommandException, RepeatedFlagNameException,
CurrentCommandDoesNotProcessFlagsException,
from ..router.exceptions import (RepeatedCommandException,
RepeatedFlagNameException,
TooManyTransferredArgsException,
RequiredArgumentNotPassedException)
RequiredArgumentNotPassedException,
IncorrectNumberOfHandlerArgsException,
TriggerCannotContainSpacesException)
class Router:
def __init__(self,
title: str = 'Commands group title:',
name: str = 'subordinate'):
name: str = 'Default'):
self.title = title
self.name = name
self._title = title
self._name = name
self._command_entities: list[dict[str, Callable[[], None] | Command]] = []
self._ignore_command_register: bool = False
self._unknown_command_handler: Callable[[str], None] | None = None
self._not_valid_flag_handler: Callable[[str], None] | None = None
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: Command) -> Callable[[Any], Any]:
command.validate_commands_params()
self._validate_command(command)
def command_decorator(func):
@@ -39,36 +39,48 @@ class Router:
return command_decorator
def set_invalid_input_flag_handler(self, func):
processed_args = getfullargspec(func).args
if len(processed_args) != 1:
raise IncorrectNumberOfHandlerArgsException()
else:
self._not_valid_flag_handler = func
def input_command_handler(self, input_command: InputCommand):
input_command_name: str = input_command.get_string_entity()
def input_command_handler(self, input_command: Command):
input_command_name: str = input_command.get_trigger()
input_command_flags: FlagsGroup = input_command.get_input_flags()
for command_entity in self._command_entities:
if input_command_name.lower() == command_entity['command'].get_string_entity().lower():
if command_entity['command'].get_flags():
if input_command.get_input_flags():
for flag in input_command.get_input_flags():
if input_command_name.lower() == command_entity['command'].get_trigger().lower():
if command_entity['command'].get_registered_flags():
if input_command_flags:
for flag in input_command_flags:
is_valid = command_entity['command'].validate_input_flag(flag)
if not is_valid:
raise InvalidInputFlagException(flag)
return command_entity['handler_func'](input_command.get_input_flags())
self._not_valid_flag_handler(flag)
return
return command_entity['handler_func'](input_command_flags.unparse_to_dict())
else:
return command_entity['handler_func'](FlagsGroup(None))
return command_entity['handler_func']({})
else:
if input_command.get_input_flags():
raise CurrentCommandDoesNotProcessFlagsException()
if input_command_flags:
self._not_valid_flag_handler(input_command_flags[0])
return
else:
return command_entity['handler_func']()
def _validate_command(self, command: Command):
command_name: str = command.get_string_entity()
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_flags()
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):
@@ -77,7 +89,7 @@ class Router:
@staticmethod
def _validate_func_args(command: Command, func: Callable):
registered_args = command.get_flags()
registered_args = command.get_registered_flags()
transferred_args = getfullargspec(func).args
if registered_args and transferred_args:
if len(transferred_args) != 1:
@@ -97,36 +109,16 @@ class Router:
def get_name(self) -> str:
return self.name
return self._name
def get_title(self) -> str:
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_handler
}
}
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_string_entity())
all_commands.append(command_entity['command'].get_trigger())
return all_commands
def get_all_flags(self) -> list[FlagsGroup]:
all_flags: list[FlagsGroup] = []
for command_entity in self._command_entities:
all_flags.append(command_entity['command'].get_flags())
return all_flags
+10 -10
View File
@@ -1,8 +1,3 @@
class InvalidDescriptionInstanceException(Exception):
def __str__(self):
return "Invalid Description Instance"
class RepeatedCommandException(Exception):
def __str__(self):
return "Commands in handler cannot be repeated"
@@ -13,11 +8,6 @@ class RepeatedFlagNameException(Exception):
return "Repeated flag name in register command"
class CurrentCommandDoesNotProcessFlagsException(Exception):
def __str__(self):
return "Current command does not process flags"
class TooManyTransferredArgsException(Exception):
def __str__(self):
return "Too many transferred arguments"
@@ -26,3 +16,13 @@ class TooManyTransferredArgsException(Exception):
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"
+11
View File
@@ -0,0 +1,11 @@
import re
def set_description_message_pattern(pattern: str) -> None:
first_check = re.match(r'.*command.*', pattern)
second_check = re.match(r'.*{description}.*', pattern)
if bool(first_check) and bool(second_check):
print('Success')
set_description_message_pattern('Invalid des{ommand}cription pattern')
+47
View File
@@ -0,0 +1,47 @@
import re
from pprint import pprint
from rich.console import Console
from argenta.command.entity import Command
from argenta.command.params.flag.entity import Flag
from argenta.command.params.flag.flags_group.entity import FlagsGroup
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()
flags = FlagsGroup(flags=[
Flag(flag_name='host',
flag_prefix='--',
possible_flag_values=re.compile(r'^192.168.\d{1,3}.\d{1,3}$')),
Flag(flag_name='port',
flag_prefix='--', )
])
@work_router.command(Command(trigger='0', description='Get Help'))
def command_help():
help_command()
@work_router.command(Command(trigger='--gbP', description='Start Solving', flags=flags))
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')
@@ -1,14 +1,18 @@
from tests.mock_app.handlers.routers import work_router, settings_router
from argenta.app.entity import App
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
from argenta.router import Router
from argenta.command import Command
from argenta.command.params.flag import Flag, FlagsGroup
app: App = App(prompt='[italic white bold]What do you want to do(enter number of action)?',
line_separate='[bold green]\n---------------------------------------------\n',
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=False)
repeat_command_groups=True)
def main():
@@ -25,6 +29,8 @@ def main():
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_string_entity()}"))
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}')
+1 -3
View File
@@ -1,6 +1,6 @@
[project]
name = "argenta"
version = "0.3.1"
version = "0.3.8"
description = "python library for creating custom shells"
authors = [
{name = "kolo", email = "kolo.is.main@gmail.com"}
@@ -33,6 +33,4 @@ numpy = "^2.2.2"
word2number = "^1.1"
numexpr = "^2.10.2"
requests = "^2.32.3"
tqdm = "^4.67.1"
setuptools = "^75.8.0"
@@ -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
View File
@@ -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]')
-44
View File
@@ -1,44 +0,0 @@
from rich.console import Console
from argenta.command.entity import Command
from argenta.command.params.flag.entity import Flag
from argenta.command.params.flag.flags_group.entity import FlagsGroup
from argenta.router import Router
from ..handlers.handlers_implementation.help_command import help_command
work_router: Router = Router(title='Work points:')
settings_router: Router = Router(title='Settings points:')
console = Console()
flagi = FlagsGroup(flags=[
Flag(flag_name='host',
flag_prefix='--', ),
Flag(flag_name='port',
flag_prefix='--', )
])
@work_router.command(Command(command='0', description='Get Help'))
def command_help():
print('Help command')
'''flags = args.get_flags()
for flag in flags:
print(f'name: "{flag.get_string_entity()}", value: "{flag.get_value()}"')'''
#help_command()
@work_router.command(Command(command='P', description='Start Solving', flags=flagi))
def command_start_solving(argrrtrts: FlagsGroup | None):
print('Solving...')
flags = argrrtrts.get_flags()
for flag in flags:
print(f'name: "{flag.get_string_entity()}", value: "{flag.get_value()}"')
#start_solving_command()
@settings_router.command(Command(command='G', description='Update WordMath'))
def command_update():
print('uefi')
# upgrade_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
View File
@@ -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}')
-17
View File
@@ -1,17 +0,0 @@
from pprint import pprint
from tests.mock_default_app.handlers.routers import work_router, settings_router
from argenta.app.entity import App
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()
-9
View File
@@ -1,9 +0,0 @@
class Entity:
pass
class Person(Entity):
pass
a: Entity = Entity()
print(a)
a = Person()
+20
View File
@@ -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()
+31
View File
@@ -0,0 +1,31 @@
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_with_flag_name_without_value(self):
with self.assertRaises(UnprocessedInputFlagException):
Command.parse_input_command('ssh --host')
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')
+75
View File
@@ -0,0 +1,75 @@
from argenta.command.params.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)
+49
View File
@@ -0,0 +1,49 @@
from argenta.command.params.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)
+133
View File
@@ -0,0 +1,133 @@
from argenta.command.params.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'