diff --git a/README.md b/README.md index fa13082..abb7dfe 100644 --- a/README.md +++ b/README.md @@ -56,14 +56,14 @@ if __name__ == '__main__': import re from argenta.router import Router from argenta.command import Command -from argenta.command.flag.registered_flag import Flags, Flag +from argenta.command.flag import Flags, Flag, InputFlags router = Router() registered_flags = Flags( - Flag(flag_name='host', - flag_prefix='--', - possible_flag_values=re.compile(r'^192.168.\d{1,3}.\d{1,3}$')), + Flag(name='host', + prefix='--', + possible_values=re.compile(r'^192.168.\d{1,3}.\d{1,3}$')), Flag('port', '--', re.compile(r'^[0-9]{1,4}$'))) @@ -75,10 +75,10 @@ def handler(): @router.command(Command(trigger="ssh", description='connect via ssh', flags=registered_flags)) -def handler_with_flags(flags: dict): +def handler_with_flags(flags: InputFlags): for flag in flags: - print(f'Flag name: {flag['name']}\n' - f'Flag value: {flag['value']}') + print(f'Flag name: {flag.get_name()}\n' + f'Flag value: {flag.get_value()}') ``` --- @@ -299,7 +299,6 @@ Router(title: str = 'Commands group title:', --- ### Исключения -- `RepeatedCommandException` - Одна и та же команда зарегистрирована в одном роутере - `RepeatedFlagNameException` - Повторяющиеся зарегистрированные флаги в команде - `TooManyTransferredArgsException` - Слишком много зарегистрированных аргументов у обработчика команды - `RequiredArgumentNotPassedException` - Не зарегистрирован обязательный аргумент у обработчика команды(аргумент, через который будут переданы флаги введённой команды) diff --git a/argenta/app/entity.py b/argenta/app/entity.py index d6c80ed..2e91c8f 100644 --- a/argenta/app/entity.py +++ b/argenta/app/entity.py @@ -52,7 +52,7 @@ class App: 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('Empty input command') - self._unknown_command_handler: Callable[[Command], None] = lambda command: print_func(f"Unknown command: {command.get_trigger()}") + self._unknown_command_handler: Callable[[InputCommand], None] = lambda command: print_func(f"Unknown command: {command.get_trigger()}") self._exit_command_handler: Callable[[], None] = lambda: print_func(self.farewell_message) @@ -78,28 +78,12 @@ class App: raw_command: str = input() try: - input_command: InputCommand = InputCommand.parse_input_command(raw_command=raw_command) - except UnprocessedInputFlagException: + input_command: InputCommand = InputCommand.parse(raw_command=raw_command) + except (UnprocessedInputFlagException, + RepeatedInputFlagsException, + EmptyInputCommandException) as error: self.print_func(self.line_separate) - self._invalid_input_flags_handler(raw_command) - self.print_func(self.line_separate) - - if not self.repeat_command_groups: - self.print_func(self.prompt) - continue - - 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._error_handler(error, raw_command) self.print_func(self.line_separate) if not self.repeat_command_groups: @@ -203,6 +187,7 @@ class App: for router in routers: self.include_router(router) + def _validate_number_of_routers(self) -> None: if not self._registered_routers: raise NoRegisteredRoutersException() @@ -259,3 +244,19 @@ class App: ) ) self.print_func(self.command_group_description_separate) + + + def _error_handler(self, + error: UnprocessedInputFlagException | + RepeatedInputFlagsException | + EmptyInputCommandException, + raw_command: str) -> None: + match error: + case UnprocessedInputFlagException(): + self._invalid_input_flags_handler(raw_command) + case RepeatedInputFlagsException(): + self._repeated_input_flags_handler(raw_command) + case EmptyInputCommandException(): + self._empty_input_command_handler() + + diff --git a/argenta/command/flag/__init__.py b/argenta/command/flag/__init__.py index e69de29..601772f 100644 --- a/argenta/command/flag/__init__.py +++ b/argenta/command/flag/__init__.py @@ -0,0 +1,4 @@ +__all__ = ('InputFlags', 'InputFlag', 'Flag', 'Flags') + + +from .models import InputFlags, InputFlag, Flags, Flag diff --git a/argenta/command/flag/defaults.py b/argenta/command/flag/defaults.py index 99caf65..24d92ee 100644 --- a/argenta/command/flag/defaults.py +++ b/argenta/command/flag/defaults.py @@ -4,7 +4,7 @@ import re @dataclass -class DefaultFlags: +class PredeterminedFlags: HELP = Flag(name='help', possible_values=False) SHORT_HELP = Flag(name='h', prefix='-', possible_values=False) diff --git a/argenta/command/flag/models.py b/argenta/command/flag/models.py index f611844..40b6615 100644 --- a/argenta/command/flag/models.py +++ b/argenta/command/flag/models.py @@ -4,11 +4,9 @@ from abc import ABC, abstractmethod class BaseFlag: def __init__(self, name: str, - prefix: Literal['-', '--', '---'] = '--', - possible_values: list[str] | Pattern[str] | False = True): + prefix: Literal['-', '--', '---'] = '--'): self._name = name self._prefix = prefix - self.possible_values = possible_values def get_string_entity(self): string_entity: str = self._prefix + self._name @@ -25,9 +23,9 @@ class BaseFlag: class InputFlag(BaseFlag): def __init__(self, name: str, prefix: Literal['-', '--', '---'] = '--', - possible_values: list[str] | Pattern[str] | False = True): - super().__init__(name, prefix, possible_values) - self._flag_value = None + value: str = None): + super().__init__(name, prefix) + self._flag_value = value def get_value(self): return self._flag_value @@ -38,6 +36,12 @@ class InputFlag(BaseFlag): class Flag(BaseFlag): + def __init__(self, name: str, + prefix: Literal['-', '--', '---'] = '--', + possible_values: list[str] | Pattern[str] | False = True): + super().__init__(name, prefix) + self.possible_values = possible_values + def validate_input_flag_value(self, input_flag_value: str | None): if self.possible_values is False: if input_flag_value is None: @@ -45,9 +49,12 @@ class Flag(BaseFlag): else: return False elif isinstance(self.possible_values, Pattern): - is_valid = bool(self.possible_values.match(input_flag_value)) - if bool(is_valid): - return True + if isinstance(input_flag_value, str): + is_valid = bool(self.possible_values.match(input_flag_value)) + if bool(is_valid): + return True + else: + return False else: return False diff --git a/argenta/command/models.py b/argenta/command/models.py index 67e25a9..5996166 100644 --- a/argenta/command/models.py +++ b/argenta/command/models.py @@ -5,30 +5,24 @@ from argenta.command.exceptions import (UnprocessedInputFlagException, from typing import Generic, TypeVar, cast, Literal -BaseCommandType = TypeVar('BaseCommandType') +InputCommandType = TypeVar('InputCommandType') -class BaseCommand(Generic[BaseCommandType]): - def __init__(self, trigger: str, - description: str = None, - flags: Flag | Flags = None): +class BaseCommand: + def __init__(self, trigger: str): self._trigger = trigger - self._description = f'description for "{self._trigger}" command' if not description else description def get_trigger(self) -> str: return self._trigger - def get_description(self) -> str: - return self._description - - class Command(BaseCommand): def __init__(self, trigger: str, description: str = None, flags: Flag | Flags = None): - super().__init__(trigger, description) + super().__init__(trigger) self._registered_flags: Flags = flags if isinstance(flags, Flags) else Flags(flags) if isinstance(flags, Flag) else Flags() + self._description = f'description for "{self._trigger}" command' if not description else description def get_registered_flags(self) -> Flags: return self._registered_flags @@ -49,13 +43,15 @@ class Command(BaseCommand): return True return False + def get_description(self) -> str: + return self._description -class InputCommand(BaseCommand): + +class InputCommand(BaseCommand, Generic[InputCommandType]): def __init__(self, trigger: str, - description: str = None, input_flags: InputFlag | InputFlags = None): - super().__init__(trigger, description) + super().__init__(trigger) self._input_flags: InputFlags = input_flags if isinstance(input_flags, InputFlags) else InputFlags(input_flags) if isinstance(input_flags, InputFlag) else InputFlags() def _set_input_flags(self, input_flags: InputFlags): @@ -65,53 +61,45 @@ class InputCommand(BaseCommand): return self._input_flags @staticmethod - def parse_input_command(raw_command: str) -> BaseCommandType: + def parse(raw_command: str) -> InputCommandType: if not raw_command: raise EmptyInputCommandException() + list_of_tokens = raw_command.split() - command = list_of_tokens[0] - list_of_tokens.pop(0) + command = list_of_tokens.pop(0) input_flags: InputFlags = InputFlags() - current_flag_name = None - current_flag_value = None + current_flag_name, current_flag_value = None, None + for k, _ in enumerate(list_of_tokens): if _.startswith('-'): - flag_prefix_last_symbol_index = _.rfind('-') - if current_flag_name or len(_) < 2 or len(_[:flag_prefix_last_symbol_index]) > 3: + if current_flag_name or len(_) < 2 or len(_[:_.rfind('-')]) > 3: raise UnprocessedInputFlagException() - else: - current_flag_name = _ + current_flag_name = _ else: if not current_flag_name: raise UnprocessedInputFlagException() - else: - current_flag_value = _ - if current_flag_name: - if not len(list_of_tokens) == k + 1: - if not list_of_tokens[k + 1].startswith('-'): - continue - flag_prefix_last_symbol_index = current_flag_name.rfind('-') - flag_prefix = current_flag_name[:flag_prefix_last_symbol_index + 1] - flag_name = current_flag_name[flag_prefix_last_symbol_index + 1:] - input_flag = InputFlag(name=flag_name, - prefix=cast(Literal['-', '--', '---'], flag_prefix)) - input_flag.set_value(current_flag_value) + current_flag_value = _ - all_flags = [x.get_string_entity() for x in input_flags.get_flags()] + if current_flag_name: + if not len(list_of_tokens) == k+1: + if not list_of_tokens[k+1].startswith('-'): continue + + input_flag = InputFlag(name=current_flag_name[current_flag_name.rfind('-')+1:], + prefix=cast(Literal['-', '--', '---'], + current_flag_name[:current_flag_name.rfind('-')+1]), + value=current_flag_value) + + all_flags = [flag.get_string_entity() for flag in input_flags.get_flags()] if input_flag.get_string_entity() not in all_flags: input_flags.add_flag(input_flag) else: raise RepeatedInputFlagsException(input_flag) - current_flag_name = None - current_flag_value = None + current_flag_name, current_flag_value = None, None + if any([current_flag_name, current_flag_value]): raise UnprocessedInputFlagException() - if len(input_flags.get_flags()) == 0: - return InputCommand(trigger=command) else: - input_command = InputCommand(trigger=command) - input_command._set_input_flags(input_flags) - return input_command + return InputCommand(trigger=command, input_flags=input_flags) diff --git a/mock/mock_app/handlers/routers.py b/mock/mock_app/handlers/routers.py index 4336245..eeb40c0 100644 --- a/mock/mock_app/handlers/routers.py +++ b/mock/mock_app/handlers/routers.py @@ -2,8 +2,8 @@ from pprint import pprint from rich.console import Console from argenta.command import Command -from argenta.command.flag.models import Flags, InputFlags -from argenta.command.flag.defaults import DefaultFlags +from argenta.command.flag import Flags, InputFlags, Flag +from argenta.command.flag.defaults import PredeterminedFlags from argenta.router import Router from .handlers_implementation.help_command import help_command @@ -22,10 +22,10 @@ def command_help(): help_command() -@work_router.command(Command(trigger='P', description='Start Solving', flags=Flags(DefaultFlags.HOST, DefaultFlags.PORT))) +@work_router.command(Command(trigger='P', description='Start Solving', flags=Flags(PredeterminedFlags.HOST, PredeterminedFlags.PORT))) def command_start_solving(args: InputFlags): print('Solving...') - pprint(args) + pprint(args.get_flag('host')) #start_solving_command() diff --git a/pyproject.toml b/pyproject.toml index 99cc662..a4600b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "argenta" -version = "0.4.1" +version = "0.4.5" description = "python library for creating custom shells" authors = [ {name = "kolo", email = "kolo.is.main@gmail.com"} diff --git a/tests/system_tests/test_system_handling_non_standard_behavior.py b/tests/system_tests/test_system_handling_non_standard_behavior.py index 541a671..162a407 100644 --- a/tests/system_tests/test_system_handling_non_standard_behavior.py +++ b/tests/system_tests/test_system_handling_non_standard_behavior.py @@ -7,8 +7,8 @@ import re from argenta.app import App from argenta.command import Command from argenta.router import Router -from argenta.command.flag.models import Flags -from argenta.command.flag.defaults import DefaultFlags +from argenta.command.flag.models import Flags, InputFlags +from argenta.command.flag.defaults import PredeterminedFlags @@ -89,11 +89,11 @@ class TestSystemHandlerNormalWork(unittest.TestCase): @patch("sys.stdout", new_callable=io.StringIO) def test_input_correct_command_with_one_correct_flag_an_one_incorrect_flag(self, mock_stdout: _io.StringIO, magick_mock: MagicMock): router = Router() - flags = Flags(DefaultFlags.HOST) + flags = Flags(PredeterminedFlags.HOST) @router.command(Command('test', flags=flags)) - def test(args: dict): - print(f'connecting to host {args["host"]["value"]}') + def test(args: InputFlags): + print(f'connecting to host {args.get_flag('host').get_value()}') app = App() app.include_router(router) @@ -185,8 +185,8 @@ class TestSystemHandlerNormalWork(unittest.TestCase): 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)) - def test(args): + @router.command(Command('test', flags=PredeterminedFlags.PORT)) + def test(args: InputFlags): print('test command') app = App() diff --git a/tests/system_tests/test_system_handling_normal_behavior.py b/tests/system_tests/test_system_handling_normal_behavior.py index 1e94a7b..c26dc26 100644 --- a/tests/system_tests/test_system_handling_normal_behavior.py +++ b/tests/system_tests/test_system_handling_normal_behavior.py @@ -8,7 +8,7 @@ from argenta.app import App from argenta.command.models import Command from argenta.router import Router from argenta.command.flag.models import Flag, Flags, InputFlags -from argenta.command.flag.defaults import DefaultFlags +from argenta.command.flag.defaults import PredeterminedFlags @@ -90,7 +90,7 @@ class TestSystemHandlerNormalWork(unittest.TestCase): @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 = PredeterminedFlags.SHORT_HELP @router.command(Command('test', flags=flag)) def test(args: dict): @@ -109,7 +109,7 @@ class TestSystemHandlerNormalWork(unittest.TestCase): @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 = PredeterminedFlags.INFO @router.command(Command('test', flags=flag)) def test(args: InputFlags): @@ -129,7 +129,7 @@ class TestSystemHandlerNormalWork(unittest.TestCase): @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 = PredeterminedFlags.HOST @router.command(Command('test', flags=flag)) def test(args: InputFlags): @@ -148,7 +148,7 @@ class TestSystemHandlerNormalWork(unittest.TestCase): @patch("sys.stdout", new_callable=io.StringIO) def test_input_correct_command_with_two_flags(self, mock_stdout: _io.StringIO, magick_mock: MagicMock): router = Router() - flags = Flags(DefaultFlags.HOST, DefaultFlags.PORT) + flags = Flags(PredeterminedFlags.HOST, PredeterminedFlags.PORT) @router.command(Command('test', flags=flags)) def test(args: InputFlags): diff --git a/tests/unit_tests/test_command.py b/tests/unit_tests/test_command.py index 24cba7e..39f3a0d 100644 --- a/tests/unit_tests/test_command.py +++ b/tests/unit_tests/test_command.py @@ -8,17 +8,17 @@ import unittest class TestInputCommand(unittest.TestCase): def test_parse_correct_raw_command(self): - self.assertEqual(InputCommand.parse_input_command('ssh --host 192.168.0.3').get_trigger(), 'ssh') + self.assertEqual(InputCommand.parse('ssh --host 192.168.0.3').get_trigger(), 'ssh') def test_parse_raw_command_without_flag_name_with_value(self): with self.assertRaises(UnprocessedInputFlagException): - InputCommand.parse_input_command('ssh 192.168.0.3') + InputCommand.parse('ssh 192.168.0.3') def test_parse_raw_command_with_repeated_flag_name(self): with self.assertRaises(RepeatedInputFlagsException): - InputCommand.parse_input_command('ssh --host 192.168.0.3 --host 172.198.0.43') + InputCommand.parse('ssh --host 192.168.0.3 --host 172.198.0.43') def test_parse_empty_raw_command(self): with self.assertRaises(EmptyInputCommandException): - InputCommand.parse_input_command('') + InputCommand.parse('')