mirror of
https://github.com/koloideal/Argenta.git
synced 2026-06-10 18:15:28 +03:00
first step to creating dream lib
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
.venv
|
||||||
|
.idea
|
||||||
|
argenta/router/__pycache__/
|
||||||
|
argenta/app/__pycache__/
|
||||||
|
argenta/__pycache__/
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
# Argon
|
# Argenta
|
||||||
python library for creating cli apps
|
python library for creating cli apps
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
from .router import *
|
||||||
|
from .app import *
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
from typing import Callable
|
||||||
|
from ..router.entity import Router
|
||||||
|
from .exceptions import (InvalidRouterInstanceException,
|
||||||
|
InvalidDescriptionMessagePatternException,
|
||||||
|
OnlyOneMainRouterIsAllowedException,
|
||||||
|
MissingMainRouterException,
|
||||||
|
MissingHandlersForUnknownCommandsOnMainRouterException,
|
||||||
|
HandlerForUnknownCommandsCanOnlyBeDeclaredForMainRouterException)
|
||||||
|
|
||||||
|
|
||||||
|
class App:
|
||||||
|
def __init__(self,
|
||||||
|
prompt: str = 'Enter a command',
|
||||||
|
exit_command: str = 'q',
|
||||||
|
ignore_exit_command_register: bool = True,
|
||||||
|
initial_greeting: str = 'Hello',
|
||||||
|
goodbye_message: str = 'GoodBye',
|
||||||
|
line_separate: str = '\n',
|
||||||
|
command_group_description_separate: str = '\n',
|
||||||
|
print_func: Callable[[str], None] = print) -> None:
|
||||||
|
self.prompt = prompt
|
||||||
|
self.print_func = print_func
|
||||||
|
self.exit_command = exit_command
|
||||||
|
self.ignore_exit_command_register = ignore_exit_command_register
|
||||||
|
self.goodbye_message = goodbye_message
|
||||||
|
self.initial_greeting = initial_greeting
|
||||||
|
self.line_separate = line_separate
|
||||||
|
self.command_group_description_separate = command_group_description_separate
|
||||||
|
|
||||||
|
self.routers: list[Router] = []
|
||||||
|
self.registered_commands: list[dict[str, str | list[dict[str, Callable[[], None] | str]] | Router]] = []
|
||||||
|
self.main_app_router: Router | None = None
|
||||||
|
self._description_message_pattern = '[{command}] *=*=* {description}'
|
||||||
|
|
||||||
|
|
||||||
|
def start_polling(self) -> None:
|
||||||
|
self.print_func(self.initial_greeting)
|
||||||
|
self.validate_main_router()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
self.print_command_group_description()
|
||||||
|
self.print_func(self.prompt)
|
||||||
|
|
||||||
|
command: str = input()
|
||||||
|
|
||||||
|
self.checking_command_for_exit_command(command)
|
||||||
|
self.print_func(self.line_separate)
|
||||||
|
|
||||||
|
is_unknown_command: bool = self.check_is_command_unknown(command)
|
||||||
|
|
||||||
|
if is_unknown_command:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for router in self.routers:
|
||||||
|
router.input_command_handler(command)
|
||||||
|
self.print_func(self.line_separate)
|
||||||
|
self.print_func(self.command_group_description_separate)
|
||||||
|
|
||||||
|
|
||||||
|
def set_initial_greeting(self, greeting: str) -> None:
|
||||||
|
self.initial_greeting = greeting
|
||||||
|
|
||||||
|
|
||||||
|
def set_goodbye_message(self, message: str) -> None:
|
||||||
|
self.goodbye_message = message
|
||||||
|
|
||||||
|
|
||||||
|
def set_description_message_pattern(self, pattern: str) -> None:
|
||||||
|
try:
|
||||||
|
pattern.format(command='command',
|
||||||
|
description='description')
|
||||||
|
except KeyError:
|
||||||
|
raise InvalidDescriptionMessagePatternException(pattern)
|
||||||
|
self._description_message_pattern = pattern
|
||||||
|
|
||||||
|
|
||||||
|
def validate_main_router(self):
|
||||||
|
if not self.main_app_router:
|
||||||
|
raise MissingMainRouterException()
|
||||||
|
|
||||||
|
if not self.main_app_router.unknown_command_func:
|
||||||
|
raise MissingHandlersForUnknownCommandsOnMainRouterException()
|
||||||
|
|
||||||
|
for router in self.routers:
|
||||||
|
if router.unknown_command_func and self.main_app_router is not router:
|
||||||
|
raise HandlerForUnknownCommandsCanOnlyBeDeclaredForMainRouterException()
|
||||||
|
|
||||||
|
|
||||||
|
def checking_command_for_exit_command(self, command: str):
|
||||||
|
if command.lower() == self.exit_command.lower():
|
||||||
|
if self.ignore_exit_command_register:
|
||||||
|
self.print_func(self.goodbye_message)
|
||||||
|
exit(0)
|
||||||
|
else:
|
||||||
|
if command == self.exit_command:
|
||||||
|
self.print_func(self.goodbye_message)
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def check_is_command_unknown(self, command: str):
|
||||||
|
registered_commands = self.registered_commands
|
||||||
|
for router in registered_commands:
|
||||||
|
for command_entity in router['commands']:
|
||||||
|
if command_entity['command'].lower() == command.lower():
|
||||||
|
if router['router'].ignore_command_register:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if command_entity['command'] == command:
|
||||||
|
return False
|
||||||
|
self.main_app_router.unknown_command_handler(command)
|
||||||
|
self.print_func(self.line_separate)
|
||||||
|
self.print_func(self.command_group_description_separate)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def print_command_group_description(self):
|
||||||
|
for router in self.registered_commands:
|
||||||
|
self.print_func(router['name'])
|
||||||
|
for command_entity in router['commands']:
|
||||||
|
self.print_func(self._description_message_pattern.format(
|
||||||
|
command=command_entity['command'],
|
||||||
|
description=command_entity['description']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.print_func(self.command_group_description_separate)
|
||||||
|
|
||||||
|
|
||||||
|
def include_router(self, router: Router, is_main: bool = False) -> None:
|
||||||
|
if not isinstance(router, Router):
|
||||||
|
raise InvalidRouterInstanceException()
|
||||||
|
|
||||||
|
if is_main:
|
||||||
|
if not self.main_app_router:
|
||||||
|
self.main_app_router = router
|
||||||
|
router.set_router_as_main()
|
||||||
|
else:
|
||||||
|
raise OnlyOneMainRouterIsAllowedException(router)
|
||||||
|
|
||||||
|
self.routers.append(router)
|
||||||
|
|
||||||
|
registered_commands: list[dict[str, Callable[[], None] | str]] = router.get_registered_commands()
|
||||||
|
self.registered_commands.append({'name': router.get_name(),
|
||||||
|
'router': router,
|
||||||
|
'commands': registered_commands})
|
||||||
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
class InvalidRouterInstanceException(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return "Invalid Router Instance"
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidDescriptionMessagePatternException(Exception):
|
||||||
|
def __init__(self, pattern: str):
|
||||||
|
self.pattern = pattern
|
||||||
|
def __str__(self):
|
||||||
|
return ("Invalid Description Message Pattern\n"
|
||||||
|
"Correct pattern example: [{command}] *=*=* {description}\n"
|
||||||
|
"The pattern must contain two variables: `command` and `description` - description of the command\n"
|
||||||
|
f"Your pattern: {self.pattern}")
|
||||||
|
|
||||||
|
|
||||||
|
class OnlyOneMainRouterIsAllowedException(Exception):
|
||||||
|
def __init__(self, existing_main_router):
|
||||||
|
self.existing_main_router = existing_main_router
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return ("Only One Main Router Allowed\n"
|
||||||
|
f"Existing main router is: {self.existing_main_router}")
|
||||||
|
|
||||||
|
|
||||||
|
class MissingMainRouterException(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return ("Missing Main Router\n"
|
||||||
|
"One of the registered routers must be the main one")
|
||||||
|
|
||||||
|
|
||||||
|
class MissingHandlersForUnknownCommandsOnMainRouterException(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return ("Missing Handlers For Unknown Commands On The Main Router\n"
|
||||||
|
"The main router must have a declared handler for unknown commands")
|
||||||
|
|
||||||
|
|
||||||
|
class HandlerForUnknownCommandsCanOnlyBeDeclaredForMainRouterException(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return '\nThe handler for unknown commands can only be declared for the main router'
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
from typing import Callable, Any
|
||||||
|
from src.core.router.exceptions import (InvalidCommandInstanceException,
|
||||||
|
UnknownCommandHandlerHasAlreadyBeenCreatedException,
|
||||||
|
InvalidDescriptionInstanceException)
|
||||||
|
|
||||||
|
|
||||||
|
class Router:
|
||||||
|
def __init__(self,
|
||||||
|
name: str,
|
||||||
|
ignore_command_register: bool = False):
|
||||||
|
|
||||||
|
self.ignore_command_register: bool = ignore_command_register
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
self.processed_commands: list[dict[str, Callable[[], None] | str]] = []
|
||||||
|
self.unknown_command_func: Callable[[str], None] | None = None
|
||||||
|
self._is_main_router: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
def command(self, command: str, description: str) -> Callable[[Any], Any]:
|
||||||
|
if not isinstance(command, str):
|
||||||
|
raise InvalidCommandInstanceException()
|
||||||
|
if not isinstance(description, str):
|
||||||
|
raise InvalidDescriptionInstanceException()
|
||||||
|
else:
|
||||||
|
def command_decorator(func):
|
||||||
|
self.processed_commands.append({'func': func,
|
||||||
|
'command': command,
|
||||||
|
'description': description})
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
return command_decorator
|
||||||
|
|
||||||
|
|
||||||
|
def unknown_command(self, func):
|
||||||
|
if self.unknown_command_func is not None:
|
||||||
|
raise UnknownCommandHandlerHasAlreadyBeenCreatedException()
|
||||||
|
|
||||||
|
self.unknown_command_func = func
|
||||||
|
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def input_command_handler(self, input_command):
|
||||||
|
for command_entity in self.processed_commands:
|
||||||
|
if input_command.lower() == command_entity['command'].lower():
|
||||||
|
if self.ignore_command_register:
|
||||||
|
return command_entity['func']()
|
||||||
|
else:
|
||||||
|
if input_command == command_entity['command']:
|
||||||
|
return command_entity['func']()
|
||||||
|
|
||||||
|
def unknown_command_handler(self, unknown_command):
|
||||||
|
self.unknown_command_func(unknown_command)
|
||||||
|
|
||||||
|
|
||||||
|
def set_router_as_main(self):
|
||||||
|
self._is_main_router = True
|
||||||
|
|
||||||
|
|
||||||
|
def get_registered_commands(self) -> list[dict[str, Callable[[], None] | str]]:
|
||||||
|
return self.processed_commands
|
||||||
|
|
||||||
|
|
||||||
|
def get_name(self) -> str:
|
||||||
|
return self._name
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
class InvalidCommandInstanceException(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return "Invalid Command Instance"
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidDescriptionInstanceException(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return "Invalid Description Instance"
|
||||||
|
|
||||||
|
|
||||||
|
class UnknownCommandHandlerHasAlreadyBeenCreatedException(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return "Only one unknown command handler can be declared"
|
||||||
Generated
+7
@@ -0,0 +1,7 @@
|
|||||||
|
# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand.
|
||||||
|
package = []
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
lock-version = "2.1"
|
||||||
|
python-versions = ">=3.11"
|
||||||
|
content-hash = "f5666f5625d676c506924a57dc0520a1f3ed2b2c774baed3dc85353594f8473d"
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
[project]
|
||||||
|
name = "argenta"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "python library for creating cli apps"
|
||||||
|
authors = [
|
||||||
|
{name = "kolo",email = "kolo.is.main@gmail.com"}
|
||||||
|
]
|
||||||
|
license = {text = "MIT"}
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
with open("README.md", "r", encoding="utf-8") as fh:
|
||||||
|
long_description = fh.read()
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="argenta",
|
||||||
|
version="0.1.0",
|
||||||
|
author="kolo",
|
||||||
|
author_email="kolo.is.main@gmail.com",
|
||||||
|
description="python library for creating cli apps",
|
||||||
|
long_description=long_description,
|
||||||
|
long_description_content_type="text/markdown",
|
||||||
|
packages=find_packages(),
|
||||||
|
install_requires=[
|
||||||
|
"requests",
|
||||||
|
],
|
||||||
|
classifiers=[
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
],
|
||||||
|
python_requires='>=3.11',
|
||||||
|
)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from argenta.app.entity import *
|
||||||
|
|
||||||
|
|
||||||
|
def test():
|
||||||
|
assert App().exit_command == 'q'
|
||||||
Reference in New Issue
Block a user