12 Commits

Author SHA1 Message Date
kolo 956febc757 v0.4.0 2025-03-15 20:50:44 +03:00
kolo beafdd0afd new system tests 2025-03-15 18:47:26 +03:00
kolo b172e2cdc3 new system tests 2025-03-15 11:42:12 +03:00
kolo baaf0e25f3 some fix 2025-03-15 00:08:39 +03:00
kolo 09c40978a1 fix 2025-03-12 13:01:15 +03:00
kolo 0f4f48555e fix 2025-03-12 00:51:11 +03:00
kolo d30515c1a2 v0.3.9 2025-03-11 20:44:06 +03:00
kolo 5a6fc1d8ca first step 2025-03-08 13:49:29 +03:00
kolo e5d6ead38e v0.3.8 2025-03-06 01:35:28 +03:00
kolo b61c151e1c v0.3.8 2025-03-06 01:35:18 +03:00
kolo a57ce490c1 v0.3.7 2025-03-06 00:15:39 +03:00
kolo edbd45f0bf some fix 2025-03-05 15:47:31 +03:00
24 changed files with 580 additions and 127 deletions
+53 -46
View File
@@ -20,7 +20,7 @@ poetry add argenta
# Быстрый старт # Быстрый старт
Пример простейшей оболочки с командой без флагов Пример простейшей оболочки с командой без зарегистрированных флагов
```python ```python
# routers.py # routers.py
from argenta.router import Router from argenta.router import Router
@@ -56,18 +56,15 @@ if __name__ == '__main__':
import re import re
from argenta.router import Router from argenta.router import Router
from argenta.command import Command from argenta.command import Command
from argenta.command.params.flag import FlagsGroup, Flag from argenta.command.flag import FlagsGroup, Flag
router = Router() router = Router()
list_of_flags = [ registered_flags = FlagsGroup(
Flag(flag_name='host', Flag(flag_name='host',
flag_prefix='--', flag_prefix='--',
possible_flag_values=re.compile(r'^192.168.\d{1,3}.\d{1,3}$')), possible_flag_values=re.compile(r'^192.168.\d{1,3}.\d{1,3}$')),
Flag(flag_name='port', Flag('port', '--', re.compile(r'^[0-9]{1,4}$')))
flag_prefix='---',
possible_flag_values=re.compile(r'^[0-9]{1,4}$'))
]
@router.command(Command("hello")) @router.command(Command("hello"))
@@ -77,10 +74,10 @@ def handler():
@router.command(Command(trigger="ssh", @router.command(Command(trigger="ssh",
description='connect via ssh', description='connect via ssh',
flags=FlagsGroup(list_of_flags))) flags=registered_flags))
def handler_with_flags(flags: dict): def handler_with_flags(flags: dict):
for flag in flags: for flag in flags:
print(f'Flag name: {flag['name']}\n print(f'Flag name: {flag['name']}\n'
f'Flag value: {flag['value']}') f'Flag value: {flag['value']}')
``` ```
@@ -90,7 +87,7 @@ def handler_with_flags(flags: dict):
--- ---
## *class* :: `App` ## *class* : : `App`
Класс, определяющий поведение и состояние оболочки Класс, определяющий поведение и состояние оболочки
### Конструктор ### Конструктор
@@ -129,52 +126,57 @@ App(prompt: str = 'Enter a command',
--- ---
**App().**`start_polling() -> None` #### **.start_polling() -> `None`**
*method mean* **::** запускает жизненный цикл приложения *method mean* **::** запускает цикл обработки ввода
--- ---
**App().**`include_router(router: Router) -> None` #### **.include_router(router: Router) -> `None`**
*param* `router: Router` **::** регистрируемый роутер *param* `router: Router` **::** регистрируемый роутер
*required* **::** True
*method mean* **::** регистрирует роутер в приложении *method mean* **::** регистрирует роутер в оболочке
--- ---
**App().**`set_initial_message(message: str) -> None` #### **.set_initial_message(message: str) -> `None`**
*param* `message: str` **::** устанавливаемое приветственное сообщение *param* `message: str` **::** устанавливаемое приветственное сообщение
*required* **::** True
*example* **::** `"Hello, I'm a example app"` *example* **::** `"Hello, I'm a example app"`
*method mean* **::** устанавливает сообщение, которое будет отображено при запуске программы *method mean* **::** устанавливает сообщение, которое будет отображено при запуске программы
--- ---
**App().**`set_farewell_message(message: str) -> None` #### **.set_farewell_message(message: str) -> `None`**
*param* `message: str` **::** устанавливаемое сообщение при выходе *param* `message: str` **::** устанавливаемое сообщение при выходе
*required* **::** True
*example* **::** `"GoodBye !"` *example* **::** `"GoodBye !"`
*method mean* **::** устанавливает сообщение, которое будет отображено при выходе *method mean* **::** устанавливает сообщение, которое будет отображено при выходе
--- ---
**App().**`set_description_message_pattern(pattern: str) -> None` #### **.set_description_message_pattern(pattern: str) -> `None`**
*param* `pattern: str` **::** паттерн описания команды при её выводе в консоль *param* `pattern: str` **::** паттерн описания команды при её выводе в консоль
*required* **::** True
*example* **::** `"[{command}] *=*=* {description}"` *example* **::** `"[{command}] *=*=* {description}"`
*method mean* **::** устанавливает паттерн описания команд, который будет использован *method mean* **::** устанавливает паттерн описания команд, который будет использован
при выводе в консоль при выводе в консоль
--- ---
<a name="custom_handler"></a>
**App().**`set_repeated_input_flags_handler(handler: Callable[[str], None]) -> None` #### **.set_repeated_input_flags_handler(handler: Callable[[str], None]) -> `None`**
*param* `handler: Callable[[str], None]` **::** функция или лямбда функция, которой будет передано управление при *param* `handler: Callable[[str], None]` **::** функция или лямбда функция, которой будет передано управление при
вводе юзером повторяющихся флагов вводе юзером повторяющихся флагов
*required* **::** True
*example* **::** `lambda raw_command: print_func(f'Repeated input flags: "{raw_command}"')` *example* **::** `lambda raw_command: print_func(f'Repeated input flags: "{raw_command}"')`
*method mean* **::** устанавливает функцию, которой будет передано управление при *method mean* **::** устанавливает функцию, которой будет передано управление при
@@ -182,10 +184,11 @@ App(prompt: str = 'Enter a command',
--- ---
**App().**`set_invalid_input_flags_handler(self, handler: Callable[[str], None]) -> None` #### **.set_invalid_input_flags_handler(self, handler: Callable[[str], None]) -> `None`**
*param* `handler: Callable[[str], None]` **::** функция или лямбда функция, которой будет передано управление при *param* `handler: Callable[[str], None]` **::** функция или лямбда функция, которой будет передано управление при
вводе юзером команды с некорректным синтаксисом флагов вводе юзером команды с некорректным синтаксисом флагов
*required* **::** True
*example* **::** `lambda raw_command: print_func(f'Incorrect flag syntax: "{raw_command}"')` *example* **::** `lambda raw_command: print_func(f'Incorrect flag syntax: "{raw_command}"')`
*method mean* **::** устанавливает функцию, которой будет передано управление при *method mean* **::** устанавливает функцию, которой будет передано управление при
@@ -193,10 +196,11 @@ App(prompt: str = 'Enter a command',
--- ---
**App().**`set_unknown_command_handler(self, handler: Callable[[str], None]) -> None` #### **.set_unknown_command_handler(self, handler: Callable[[str], None]) -> `None`**
*param* `handler: Callable[[str], None]` **::** функция или лямбда функция, которой будет передано управление при *param* `handler: Callable[[str], None]` **::** функция или лямбда функция, которой будет передано управление при
вводе юзером неизвестной команды вводе юзером неизвестной команды
*required* **::** True
*example* **::** `lambda command: print_func(f"Unknown command: {command.get_string_entity()}")` *example* **::** `lambda command: print_func(f"Unknown command: {command.get_string_entity()}")`
*method mean* **::** устанавливает функцию, которой будет передано управление при *method mean* **::** устанавливает функцию, которой будет передано управление при
@@ -204,10 +208,11 @@ App(prompt: str = 'Enter a command',
--- ---
**App().**`set_empty_command_handler(self, handler: Callable[[str], None]) -> None` #### **.set_empty_command_handler(self, handler: Callable[[str], None]) -> `None`**
*param* `handler: Callable[[str], None]` **::** функция или лямбда функция, которой будет передано управление при *param* `handler: Callable[[str], None]` **::** функция или лямбда функция, которой будет передано управление при
вводе юзером пустой команды вводе юзером пустой команды
*required* **::** True
*example* **::** `lambda: print_func(f'Empty input command')` *example* **::** `lambda: print_func(f'Empty input command')`
*method mean* **::** устанавливает функцию, которой будет передано управление при *method mean* **::** устанавливает функцию, которой будет передано управление при
@@ -219,10 +224,10 @@ App(prompt: str = 'Enter a command',
- В устанавливаемом паттерне сообщения описания команды необходимы быть два ключевых слова: - В устанавливаемом паттерне сообщения описания команды необходимы быть два ключевых слова:
`command` и `description`, каждое из которых должно быть заключено в фигурные скобки, после обработки `command` и `description`, каждое из которых должно быть заключено в фигурные скобки, после обработки
паттерна на места этих ключевых слов будут подставлены соответствующие значения команды, при отсутствии паттерна на места этих ключевых слов будут подставлены соответствующие атрибуты команды, при отсутствии
этих двух ключевых слов будет вызвано исключение `InvalidDescriptionMessagePatternException` этих двух ключевых слов будет вызвано исключение `InvalidDescriptionMessagePatternException`
- Команды приложения не должны повторяться, при значении атрибута `ignore_command_register` равным `True` - Команды оболочки не должны повторяться, при значении атрибута `ignore_command_register` равным `True`
допускается создание обработчиков для разных регистров одинаковых символов в команде, для примера `u` и `U`, допускается создание обработчиков для разных регистров одинаковых символов в команде, для примера `u` и `U`,
при значении атрибута `ignore_command_register` класса `App` равным `False` тот же пример вызывает исключение при значении атрибута `ignore_command_register` класса `App` равным `False` тот же пример вызывает исключение
`RepeatedCommandInDifferentRoutersException`. Исключение вызывается только при наличии пересекающихся команд `RepeatedCommandInDifferentRoutersException`. Исключение вызывается только при наличии пересекающихся команд
@@ -248,7 +253,7 @@ App(prompt: str = 'Enter a command',
### Конструктор ### Конструктор
```python ```python
Router(title: str = 'Commands group title:', Router(title: str = 'Commands group title:',
name: str = 'subordinate') name: str = 'Default')
``` ```
@@ -264,29 +269,30 @@ Router(title: str = 'Commands group title:',
--- ---
**`@`Router().**`command(command: Command)` #### **command(command: Command)**
*param* `command: Command` **::** экземпляр класса `Command`, который определяет строковый триггер команды, *param* `command: Command` **::** экземпляр класса `Command`, который определяет строковый триггер команды,
допустимые флаги команды и другое допустимые флаги команды и другое
*required* **::** True
*example* **::** `Command(command='ssh', description='connect via ssh')` *example* **::** `Command(command='ssh', description='connect via ssh')`
*method mean* **::** декоратор, который регистрирует функцию как обработчик команды *method mean* **::** декоратор, который регистрирует функцию как обработчик команды
--- ---
**Router().**`get_name() -> str` #### **.get_name() -> `str`**
*method mean* **::** возвращает установленное название роутера *method mean* **::** возвращает установленное название роутера
--- ---
**Router().**`get_title() -> str` #### **.get_title() -> `str`**
*method mean* **::** возвращает установленный заголовок группы команд данного роутера *method mean* **::** возвращает установленный заголовок группы команд данного роутера
--- ---
**Router().**`get_all_commands() -> list[str]` #### **.get_all_commands() -> `list[str]`**
*method mean* **::** возвращает все зарегистрированные команды для данного роутера *method mean* **::** возвращает все зарегистрированные команды для данного роутера
@@ -298,6 +304,7 @@ Router(title: str = 'Commands group title:',
- `TooManyTransferredArgsException` - Слишком много зарегистрированных аргументов у обработчика команды - `TooManyTransferredArgsException` - Слишком много зарегистрированных аргументов у обработчика команды
- `RequiredArgumentNotPassedException` - Не зарегистрирован обязательный аргумент у обработчика команды(аргумент, через который будут переданы флаги введённой команды) - `RequiredArgumentNotPassedException` - Не зарегистрирован обязательный аргумент у обработчика команды(аргумент, через который будут переданы флаги введённой команды)
- `IncorrectNumberOfHandlerArgsException` - У обработчика нестандартного поведения зарегистрировано неверное количество аргументов(в большинстве случаев у него должен быть один аргумент) - `IncorrectNumberOfHandlerArgsException` - У обработчика нестандартного поведения зарегистрировано неверное количество аргументов(в большинстве случаев у него должен быть один аргумент)
- `TriggerCannotContainSpacesException` - У регистрируемой команды в триггере содержатся пробелы
--- ---
@@ -319,19 +326,19 @@ Command(trigger: str,
--- ---
**Command().**`get_trigger() -> str` #### **.get_trigger() -> `str`**
*method mean* **::** возвращает строковый триггер экземпляра *method mean* **::** возвращает строковый триггер экземпляра
--- ---
**Command().**`get_description() -> str` #### **.get_description() -> `str`**
*method mean* **::** возвращает описание команды *method mean* **::** возвращает описание команды
--- ---
**Command().**`get_registered_flags() -> FlagsGroup | None` #### **.get_registered_flags() -> `FlagsGroup | None`**
*method mean* **::** возвращает зарегистрированные флаги экземпляра *method mean* **::** возвращает зарегистрированные флаги экземпляра
@@ -345,20 +352,19 @@ Command(trigger: str,
**Примечание** **Примечание**
Все вышеуказанные исключения класса `Command` вызываются в рантайме запущенным экземпляром класса Все вышеуказанные исключения класса `Command` вызываются в рантайме запущенным экземпляром класса
`App`, а также по дефолту обрабатываются, при желании можно задать пользовательские `App`, а также по дефолту обрабатываются, при желании можно задать пользовательские
обработчики для этих исключений ([подробнее см.](#methods-)) обработчики для этих исключений ([подробнее см.](#custom_handler))
--- ---
## *class* :: `Flag` ## *class* :: `Flag`
Класс, экземпляры которого в большинстве случаев должны передаваться при создании Класс, экземпляры которого в большинстве случаев передаются при создании
экземпляра класса `Command` для регистрации допустимого флага при вводе юзером команды экземпляра класса `Command` для регистрации допустимого флага при вводе юзером команды
### Конструктор ### Конструктор
```python ```python
Flag(flag_name: str, Flag(flag_name: str,
flag_prefix: Literal['-', '--', '---'] = '-', flag_prefix: typing.Literal['-', '--', '---'] = '-',
ignore_flag_value_register: bool = False, possible_flag_values: list[str] | typing.Pattern[str] | False = True)
possible_flag_values: list[str] | Pattern[str] = False)
``` ```
--- ---
@@ -367,9 +373,10 @@ Flag(flag_name: str,
- **name : mean** - **name : mean**
- `flag_name` (`str`): Имя флага - `flag_name` (`str`): Имя флага
- `flag_prefix` (`Literal['-', '--', '---']`): Префикс команды, допустимым значением является от одного до трёх минусов - `flag_prefix` (`Literal['-', '--', '---']`): Префикс команды, допустимым значением является от одного до трёх минусов
- `ignore_flag_value_register` (`bool`): Будет ли игнорироваться регистр значения введённого флага - `possible_flag_values` (`list[str] | Pattern[str] | bool`): Множество допустимых значений флага, может быть задано
- `possible_flag_values` (`list[str] | Pattern[str]`): Множество допустимых значений флага, может быть задано списком с допустимыми значениями или регулярным выражением (рекомендуется `re.compile(r'example exspression')`), при значении
списком с допустимыми значениями или регулярным выражением (рекомендуется `re.compile(r'example exspression')`) аргумента `False` у введённого флага не может быть значения, иначе будет вызвано исключение и обработано соответствующим
еррор-хэндлером
--- ---
@@ -377,19 +384,19 @@ Flag(flag_name: str,
--- ---
**Flag().**`get_sring_entity() -> str` #### **.get_string_entity() -> `str`**
*method mean* **::** возвращает строковое представление флага(префикс + имя) *method mean* **::** возвращает строковое представление флага(префикс + имя)
--- ---
**Flag().**`get_flag_name() -> str` #### **.get_flag_name() -> `str`**
*method mean* **::** возвращает имя флага *method mean* **::** возвращает имя флага
--- ---
**Flag().**`get_flag_prefix() -> str` #### **.get_flag_prefix() -> `str`**
*method mean* **::** возвращает префикс флага *method mean* **::** возвращает префикс флага
@@ -402,14 +409,14 @@ Flag(flag_name: str,
### Конструктор ### Конструктор
```python ```python
FlagsGroup(flags: list[Flag] = None) FlagsGroup(*flagы: Flag)
``` ```
--- ---
**Аргументы:** **Аргументы:**
- **name : mean** - **name : mean**
- `flags` (`list[Flag]`): Список флагов, которые будут объединены в одну группу - `*flags` (`Flag`): Неограниченное количество передаваемых флагов
--- ---
@@ -417,7 +424,7 @@ FlagsGroup(flags: list[Flag] = None)
--- ---
**FlagsGroup().**`get_flags() -> list[Flag]` #### **.get_flags() -> `list[Flag]`**
*method mean* **::** возвращает зарегистрированные флаги *method mean* **::** возвращает зарегистрированные флаги
+8 -5
View File
@@ -50,7 +50,7 @@ class App:
self._invalid_input_flags_handler: Callable[[str], None] = lambda raw_command: print_func(f'Incorrect flag syntax: "{raw_command}"') 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._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._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()}") self._unknown_command_handler: Callable[[Command], None] = lambda command: print_func(f"Unknown command: {command.get_trigger()}")
def start_polling(self) -> None: def start_polling(self) -> None:
@@ -100,7 +100,9 @@ class App:
self.print_func(self.prompt) self.print_func(self.prompt)
continue continue
self._check_command_for_exit_command(input_command.get_trigger()) is_exit = self._is_exit_command(input_command.get_trigger())
if is_exit:
return
self.print_func(self.line_separate) self.print_func(self.line_separate)
is_unknown_command: bool = self._check_is_command_unknown(input_command) is_unknown_command: bool = self._check_is_command_unknown(input_command)
@@ -212,15 +214,16 @@ class App:
raise RepeatedCommandInDifferentRoutersException() raise RepeatedCommandInDifferentRoutersException()
def _check_command_for_exit_command(self, command: str): def _is_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.farewell_message) self.print_func(self.farewell_message)
exit(0) return True
else: else:
if command == self.exit_command: if command == self.exit_command:
self.print_func(self.farewell_message) self.print_func(self.farewell_message)
exit(0) return True
return False
def _check_is_command_unknown(self, command: Command): def _check_is_command_unknown(self, command: Command):
+13 -11
View File
@@ -1,23 +1,22 @@
from .params.flag.entity import Flag from argenta.command.flag.entity import Flag
from .params.flag.flags_group.entity import FlagsGroup from argenta.command.flag.flags_group import FlagsGroup
from .exceptions import (UnprocessedInputFlagException, from .exceptions import (UnprocessedInputFlagException,
RepeatedInputFlagsException, RepeatedInputFlagsException,
EmptyInputCommandException) EmptyInputCommandException)
from typing import Generic, TypeVar from typing import Generic, TypeVar, cast, Literal
T = TypeVar('T') CommandType = TypeVar('CommandType')
class Command(Generic[T]): class Command(Generic[CommandType]):
def __init__(self, trigger: str, def __init__(self, trigger: str,
description: str = None, description: str = None,
flags: Flag | FlagsGroup = None): flags: Flag | FlagsGroup = None):
self._trigger = trigger self._trigger = trigger
self._description = f'description for "{self._trigger}" command' if not description else description 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._registered_flags: FlagsGroup | None = flags if isinstance(flags, FlagsGroup) else FlagsGroup(flags) if isinstance(flags, Flag) else flags
self._input_flags: FlagsGroup | None = None self._input_flags: FlagsGroup | None = None
@@ -57,7 +56,7 @@ class Command(Generic[T]):
return self._input_flags return self._input_flags
@staticmethod @staticmethod
def parse_input_command(raw_command: str) -> 'Command[T]': def parse_input_command(raw_command: str) -> CommandType:
if not raw_command: if not raw_command:
raise EmptyInputCommandException() raise EmptyInputCommandException()
list_of_tokens = raw_command.split() list_of_tokens = raw_command.split()
@@ -67,7 +66,7 @@ class Command(Generic[T]):
flags: FlagsGroup = FlagsGroup() flags: FlagsGroup = FlagsGroup()
current_flag_name = None current_flag_name = None
current_flag_value = None current_flag_value = None
for _ in list_of_tokens: for k, _ in enumerate(list_of_tokens):
if _.startswith('-'): if _.startswith('-'):
flag_prefix_last_symbol_index = _.rfind('-') flag_prefix_last_symbol_index = _.rfind('-')
if current_flag_name or len(_) < 2 or len(_[:flag_prefix_last_symbol_index]) > 3: if current_flag_name or len(_) < 2 or len(_[:flag_prefix_last_symbol_index]) > 3:
@@ -79,12 +78,15 @@ class Command(Generic[T]):
raise UnprocessedInputFlagException() raise UnprocessedInputFlagException()
else: else:
current_flag_value = _ current_flag_value = _
if current_flag_name and 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_last_symbol_index = current_flag_name.rfind('-')
flag_prefix = current_flag_name[:flag_prefix_last_symbol_index+1] flag_prefix = current_flag_name[:flag_prefix_last_symbol_index+1]
flag_name = 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, input_flag = Flag(flag_name=flag_name,
flag_prefix=flag_prefix) flag_prefix=cast(Literal['-', '--', '---'], flag_prefix))
input_flag.set_value(current_flag_value) input_flag.set_value(current_flag_value)
all_flags = [x.get_string_entity() for x in flags.get_flags()] all_flags = [x.get_string_entity() for x in flags.get_flags()]
+1 -1
View File
@@ -1,4 +1,4 @@
from .params.flag.entity import Flag from argenta.command.flag.entity import Flag
class UnprocessedInputFlagException(Exception): class UnprocessedInputFlagException(Exception):
+21
View File
@@ -0,0 +1,21 @@
from dataclasses import dataclass
from argenta.command.flag import Flag
import re
@dataclass
class DefaultFlags:
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}$'))
@@ -4,12 +4,10 @@ from typing import Literal, Pattern
class Flag: class Flag:
def __init__(self, flag_name: str, def __init__(self, flag_name: str,
flag_prefix: Literal['-', '--', '---'] = '--', flag_prefix: Literal['-', '--', '---'] = '--',
ignore_flag_value_register: bool = False, possible_flag_values: list[str] | Pattern[str] | False = True):
possible_flag_values: list[str] | Pattern[str] = False):
self._flag_name = flag_name self._flag_name = flag_name
self._flag_prefix = flag_prefix self._flag_prefix = flag_prefix
self.possible_flag_values = possible_flag_values self.possible_flag_values = possible_flag_values
self.ignore_flag_value_register = ignore_flag_value_register
self._flag_value = None self._flag_value = None
@@ -29,23 +27,23 @@ class Flag:
def set_value(self, value): def set_value(self, value):
self._flag_value = value self._flag_value = value
def validate_input_flag_value(self, input_flag_value: str): def validate_input_flag_value(self, input_flag_value: str | None):
if isinstance(self.possible_flag_values, Pattern): 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)) is_valid = bool(self.possible_flag_values.match(input_flag_value))
if bool(is_valid): if bool(is_valid):
return True return True
else: else:
return False return False
if isinstance(self.possible_flag_values, list): elif isinstance(self.possible_flag_values, list):
if self.ignore_flag_value_register: if input_flag_value in self.possible_flag_values:
if input_flag_value.lower() in [x.lower() for x in self.possible_flag_values]: return True
return True
else:
return False
else: else:
if input_flag_value in self.possible_flag_values: return False
return True else:
else: return True
return False
return True
@@ -0,0 +1 @@
from .entity import FlagsGroup
@@ -1,8 +1,8 @@
from argenta.command.params.flag.entity import Flag from argenta.command.flag import Flag
class FlagsGroup: class FlagsGroup:
def __init__(self, flags: list[Flag] = None): def __init__(self, *flags: Flag):
self._flags: list[Flag] = [] if not flags else flags self._flags: list[Flag] = [] if not flags else flags
def get_flags(self) -> list[Flag]: def get_flags(self) -> list[Flag]:
+8 -5
View File
@@ -2,19 +2,20 @@ from typing import Callable, Any
from inspect import getfullargspec from inspect import getfullargspec
from ..command.entity import Command from ..command.entity import Command
from ..command.params.flag.entity import Flag from argenta.command.flag.entity import Flag
from ..command.params.flag.flags_group.entity import FlagsGroup from argenta.command.flag.flags_group import FlagsGroup
from ..router.exceptions import (RepeatedCommandException, from ..router.exceptions import (RepeatedCommandException,
RepeatedFlagNameException, RepeatedFlagNameException,
TooManyTransferredArgsException, TooManyTransferredArgsException,
RequiredArgumentNotPassedException, RequiredArgumentNotPassedException,
IncorrectNumberOfHandlerArgsException) IncorrectNumberOfHandlerArgsException,
TriggerCannotContainSpacesException)
class Router: class Router:
def __init__(self, def __init__(self,
title: str = 'Commands group title:', title: str = 'Commands group title:',
name: str = 'subordinate'): name: str = 'Default'):
self._title = title self._title = title
self._name = name self._name = name
@@ -22,7 +23,7 @@ class Router:
self._command_entities: list[dict[str, Callable[[], None] | Command]] = [] self._command_entities: list[dict[str, Callable[[], None] | Command]] = []
self._ignore_command_register: bool = False self._ignore_command_register: bool = False
self._not_valid_flag_handler: Callable[[Flag], None] = lambda flag: print(f"Undefined or incorrect input flag: '{flag.get_string_entity()} {flag.get_value()}'") self._not_valid_flag_handler: Callable[[Flag], None] = lambda flag: print(f"Undefined or incorrect input flag: {flag.get_string_entity()}{(' '+flag.get_value()) if flag.get_value() else ''}")
def command(self, command: Command) -> Callable[[Any], Any]: def command(self, command: Command) -> Callable[[Any], Any]:
@@ -71,6 +72,8 @@ class Router:
def _validate_command(self, command: Command): def _validate_command(self, command: Command):
command_name: str = command.get_trigger() command_name: str = command.get_trigger()
if command_name.find(' ') != -1:
raise TriggerCannotContainSpacesException()
if command_name in self.get_all_commands(): if command_name in self.get_all_commands():
raise RepeatedCommandException() raise RepeatedCommandException()
if self._ignore_command_register: if self._ignore_command_register:
+5
View File
@@ -21,3 +21,8 @@ class RequiredArgumentNotPassedException(Exception):
class IncorrectNumberOfHandlerArgsException(Exception): class IncorrectNumberOfHandlerArgsException(Exception):
def __str__(self): def __str__(self):
return "Handler has incorrect number of arguments" return "Handler has incorrect number of arguments"
class TriggerCannotContainSpacesException(Exception):
def __str__(self):
return "Command trigger cannot contain spaces"
+3 -7
View File
@@ -1,11 +1,7 @@
import re import re
def set_description_message_pattern(pattern: str) -> None: def test(string):
first_check = re.match(r'.*command.*', pattern) return bool(re.match(r'\ntest command\n(.|\s)*\nsome command\n', string))
second_check = re.match(r'.*{description}.*', pattern)
if bool(first_check) and bool(second_check):
print('Success')
print(test('test command tpgm4tigm4tigmt\n i0hhmi6h some command'))
set_description_message_pattern('Invalid des{ommand}cription pattern')
+5 -14
View File
@@ -2,15 +2,15 @@ import re
from pprint import pprint from pprint import pprint
from rich.console import Console from rich.console import Console
from argenta.command.entity import Command from argenta.command import Command
from argenta.command.params.flag.entity import Flag from argenta.command.flag import Flag, FlagsGroup
from argenta.command.params.flag.flags_group.entity import FlagsGroup from argenta.command.flag.defaults import DefaultFlags
from argenta.router import Router from argenta.router import Router
from .handlers_implementation.help_command import help_command from .handlers_implementation.help_command import help_command
work_router: Router = Router(title='Work nts:') 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()}"')) work_router.set_invalid_input_flag_handler(lambda flag: print(f'Invalid input flag: {flag.get_string_entity()} {flag.get_value() if flag.get_value() else ''}'))
settings_router: Router = Router(title='Settings points:') settings_router: Router = Router(title='Settings points:')
@@ -18,21 +18,12 @@ settings_router: Router = Router(title='Settings points:')
console = Console() 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')) @work_router.command(Command(trigger='0', description='Get Help'))
def command_help(): def command_help():
help_command() help_command()
@work_router.command(Command(trigger='P', description='Start Solving', flags=flags)) @work_router.command(Command(trigger='P', description='Start Solving', flags=FlagsGroup(DefaultFlags.host_flag, DefaultFlags.port_flag)))
def command_start_solving(args: dict): def command_start_solving(args: dict):
print('Solving...') print('Solving...')
pprint(args) pprint(args)
+3 -6
View File
@@ -3,16 +3,13 @@ from art import text2art
from rich.console import Console from rich.console import Console
from argenta.app import App 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)?', 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', line_separate=f'\n{"[bold green]-[/bold green][bold red]-[/bold red]"*25}\n',
print_func=Console().print, print_func=Console().print,
command_group_description_separate='', command_group_description_separate='',
repeat_command_groups=True) repeat_command_groups=True,
ignore_exit_command_register=False)
def main(): def main():
@@ -29,7 +26,7 @@ def main():
app.set_farewell_message(goodbye_message) app.set_farewell_message(goodbye_message)
app.set_invalid_input_flags_handler(lambda raw_command: print(f"Invalid input flags: {raw_command}")) 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_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_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.set_description_message_pattern('[bold red][{command}][/bold red] [blue]*=*=*[/blue] [bold yellow italic]{description}')
+2 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "argenta" name = "argenta"
version = "0.3.6" version = "0.4.0"
description = "python library for creating custom shells" 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"}
@@ -33,4 +33,5 @@ numpy = "^2.2.2"
word2number = "^1.1" word2number = "^1.1"
numexpr = "^2.10.2" numexpr = "^2.10.2"
requests = "^2.32.3" requests = "^2.32.3"
pyreadline3 = "^3.5.4"
@@ -0,0 +1,198 @@
import _io
from unittest.mock import patch, MagicMock
import unittest
import io
import re
from argenta.app import App
from argenta.command import Command
from argenta.router import Router
from argenta.command.flag import Flag, FlagsGroup
from argenta.command.flag.defaults import DefaultFlags
class TestSystemHandlerNormalWork(unittest.TestCase):
@patch("builtins.input", side_effect=["help", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_incorrect_command(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print('test command')
app = App()
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn("\nUnknown command: help\n", output)
@patch("builtins.input", side_effect=["TeSt", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_incorrect_command2(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print('test command')
app = App(ignore_command_register=False)
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\nUnknown command: TeSt\n', output)
@patch("builtins.input", side_effect=["test --help", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command_with_unregistered_flag(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print(f'test command')
app = App()
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\nUndefined or incorrect input flag: --help\n', output)
@patch("builtins.input", side_effect=["test --port 22", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command_with_unregistered_flag2(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print('test command')
app = App()
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\nUndefined or incorrect input flag: --port 22\n', output)
@patch("builtins.input", side_effect=["test --host 192.168.32.1 --port 132", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command_with_one_correct_flag_an_one_incorrect_flag(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
flags = FlagsGroup(DefaultFlags.host_flag)
@router.command(Command('test', flags=flags))
def test(args: dict):
print(f'connecting to host {args["host"]["value"]}')
app = App()
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\nUndefined or incorrect input flag: --port 132\n', output)
@patch("builtins.input", side_effect=["test", "some", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_one_correct_command_and_one_incorrect_command(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print(f'test command')
app = App()
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertRegex(output, re.compile(r'\ntest command\n(.|\n)*\nUnknown command: some\n'))
@patch("builtins.input", side_effect=["test", "some", "more", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_two_correct_commands_and_one_incorrect_command(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print(f'test command')
@router.command(Command('more'))
def test():
print(f'more command')
app = App()
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertRegex(output, re.compile(r'\ntest command\n(.|\n)*\nUnknown command: some\n(.|\n)*\nmore command'))
@patch("builtins.input", side_effect=["test 535 --port", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command_with_incorrect_flag(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print(f'test command')
app = App()
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn("\nIncorrect flag syntax: \"test 535 --port\"\n", output)
@patch("builtins.input", side_effect=["", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_empty_command(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print(f'test command')
app = App()
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn("\nEmpty input command\n", output)
@patch("builtins.input", side_effect=["test --port 22 --port 33", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command_with_repeated_flags(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test', flags=DefaultFlags.port_flag))
def test(args):
print('test command')
app = App()
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn("\nRepeated input flags: \"test --port 22 --port 33\"", output)
@@ -0,0 +1,211 @@
import _io
from unittest.mock import patch, MagicMock
import unittest
import io
import re
from argenta.app import App
from argenta.command import Command
from argenta.router import Router
from argenta.command.flag import Flag, FlagsGroup
from argenta.command.flag.defaults import DefaultFlags
class TestSystemHandlerNormalWork(unittest.TestCase):
@patch("builtins.input", side_effect=["test", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print('test command')
app = App()
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\ntest command\n', output)
@patch("builtins.input", side_effect=["TeSt", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command2(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print('test command')
app = App(ignore_command_register=True)
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\ntest command\n', output)
@patch("builtins.input", side_effect=["test --help", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command_with_custom_flag(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
flag = Flag('help', '--', False)
@router.command(Command('test', flags=flag))
def test(args: dict):
print(f'\nhelp for {args['help']['name']} flag\n')
app = App()
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\nhelp for help flag\n', output)
@patch("builtins.input", side_effect=["test --port 22", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command_with_custom_flag2(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
flag = Flag('port', '--', re.compile(r'^\d{1,5}$'))
@router.command(Command('test', flags=flag))
def test(args: dict):
print(f'flag value for {args['port']['name']} flag : {args["port"]["value"]}')
app = App()
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\nflag value for port flag : 22\n', output)
@patch("builtins.input", side_effect=["test -h", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command_with_default_flag(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
flag = DefaultFlags.short_help_flag
@router.command(Command('test', flags=flag))
def test(args: dict):
print(f'help for {args['h']['name']} flag')
app = App()
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\nhelp for h flag\n', output)
@patch("builtins.input", side_effect=["test --info", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command_with_default_flag2(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
flag = DefaultFlags.info_flag
@router.command(Command('test', flags=flag))
def test(args: dict):
if args.get('info'):
print('info about test command')
app = App()
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\ninfo about test command\n', output)
@patch("builtins.input", side_effect=["test --host 192.168.0.1", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command_with_default_flag3(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
flag = DefaultFlags.host_flag
@router.command(Command('test', flags=flag))
def test(args: dict):
print(f'connecting to host {args["host"]["value"]}')
app = App()
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\nconnecting to host 192.168.0.1\n', output)
@patch("builtins.input", side_effect=["test --host 192.168.32.1 --port 132", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_correct_command_with_two_flags(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
flags = FlagsGroup(DefaultFlags.host_flag, DefaultFlags.port_flag)
@router.command(Command('test', flags=flags))
def test(args: dict):
print(f'connecting to host {args["host"]["value"]} and port {args["port"]["value"]}')
app = App()
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertIn('\nconnecting to host 192.168.32.1 and port 132\n', output)
@patch("builtins.input", side_effect=["test", "some", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_two_correct_command(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print(f'test command')
@router.command(Command('some'))
def test():
print(f'some command')
app = App()
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertRegex(output, re.compile(r'\ntest command\n(.|\n)*\nsome command\n'))
@patch("builtins.input", side_effect=["test", "some", "more", "q"])
@patch("sys.stdout", new_callable=io.StringIO)
def test_input_three_correct_command(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
router = Router()
@router.command(Command('test'))
def test():
print(f'test command')
@router.command(Command('some'))
def test():
print(f'some command')
@router.command(Command('more'))
def test():
print(f'more command')
app = App()
app.include_router(router)
app.start_polling()
output = mock_stdout.getvalue()
self.assertRegex(output, re.compile(r'\ntest command\n(.|\n)*\nsome command\n(.|\n)*\nmore command'))
@@ -10,10 +10,6 @@ class TestCommand(unittest.TestCase):
def test_parse_correct_raw_command(self): def test_parse_correct_raw_command(self):
self.assertEqual(Command.parse_input_command('ssh --host 192.168.0.3').get_trigger(), 'ssh') 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): def test_parse_raw_command_without_flag_name_with_value(self):
with self.assertRaises(UnprocessedInputFlagException): with self.assertRaises(UnprocessedInputFlagException):
Command.parse_input_command('ssh 192.168.0.3') Command.parse_input_command('ssh 192.168.0.3')
@@ -1,4 +1,4 @@
from argenta.command.params.flag import Flag from argenta.command.flag import Flag
import unittest import unittest
import re import re
@@ -52,6 +52,22 @@ class TestFlag(unittest.TestCase):
flag = Flag(flag_name='test', possible_flag_values=re.compile(r'192.168.\d+.\d+')) 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) 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)
@@ -1,4 +1,4 @@
from argenta.command.params.flag import Flag, FlagsGroup from argenta.command.flag import Flag, FlagsGroup
import unittest import unittest
@@ -31,7 +31,7 @@ class TestFlagsGroup(unittest.TestCase):
Flag('test2'), Flag('test2'),
Flag('test3'), Flag('test3'),
] ]
flags = FlagsGroup(list_of_flags) flags = FlagsGroup(*list_of_flags)
serialized_flags = flags.unparse_to_dict() serialized_flags = flags.unparse_to_dict()
needed_result = {'test1': {'name': 'test1', needed_result = {'test1': {'name': 'test1',
'prefix': '--', 'prefix': '--',
@@ -1,7 +1,7 @@
from argenta.command.params.flag import FlagsGroup, Flag from argenta.command.flag import FlagsGroup, Flag
from argenta.router import Router from argenta.router import Router
from argenta.command import Command from argenta.command import Command
from argenta.router.exceptions import RepeatedCommandException from argenta.router.exceptions import RepeatedCommandException, TriggerCannotContainSpacesException
import unittest import unittest
@@ -109,6 +109,13 @@ class TestRouter(unittest.TestCase):
def test(): def test():
return 'correct result' 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'