initial commit

This commit is contained in:
Bacruru Sakaguchi
2025-09-12 17:10:13 +07:00
commit 9e5e214944
57 changed files with 1538 additions and 0 deletions

85
shell/Handlers/ABS.py Normal file
View File

@@ -0,0 +1,85 @@
from pathlib import Path
from modelspace.Repository import Repository
global_repo = Repository(str(Path('..') / 'repo'))
class ExecutionError(RuntimeError): pass
class Handler:
syntax_error = SyntaxError
execution_error = ExecutionError
def __init__(self):
self.forwarding_table: dict[str, Handler] = {}
self.handle_table: dict = {}
self.succeed = False
pass
@staticmethod
def _check_arg(keys: dict, arg: str):
if not keys.get(arg, None): raise TypeError('Unfilled argument:', arg)
def handle(self, command: list[str], pos = 0):
self.succeed = False
if len(command) <= pos: raise self.syntax_error
verb = command[pos].lower()
if verb in self.forwarding_table:
self.forwarding_table[verb].handle(command, pos + 1)
if self.forwarding_table[verb].succeed:
self.succeed = True
return
elif verb in self.handle_table:
self.handle_table[verb](command, pos + 1)
if self.succeed:
return
@staticmethod
def parse_arguments(args_list: list[str], expected_args: list[str], key_mode=False) -> tuple[dict[str, str | None], list[str]]:
"""
Парсит список аргументов согласно ожидаемым именам.
Args:
args_list: список аргументов для обработки
expected_args: список ожидаемых имен аргументов
Returns:
Кортеж из двух элементов:
- Словарь с ключами из expected_args и значениями аргументов (или None)
- Список необработанных аргументов
"""
# Инициализируем результат значениями None для всех ожидаемых аргументов
result: dict[str, str | None] = {arg: None for arg in expected_args}
# Извлекаем именованные аргументы (в формате "имя:значение")
remaining_args = []
for arg in args_list:
if ':' in arg:
key, value = arg.split(':', 1)
if key in expected_args:
result[key] = value
else:
# Если имя аргумента не ожидается, добавляем в оставшиеся
remaining_args.append(arg)
else:
remaining_args.append(arg)
# Заполняем оставшиеся аргументы по порядку
arg_index = 0
if not key_mode:
for arg_name in expected_args:
if result[arg_name] is None and arg_index < len(remaining_args):
result[arg_name] = remaining_args[arg_index]
arg_index += 1
# Все оставшиеся аргументы добавляем в unsorted
unsorted = remaining_args[arg_index:]
return result, unsorted

View File

@@ -0,0 +1,27 @@
from shell.Handlers.ABS import Handler
from shell.Handlers.PythonappHandler import PythonappHandler
from shell.Handlers.ModelSpaceHandler import ModelSpaceHandler
class GlobalHandler(Handler):
def __init__(self):
super().__init__()
self.forwarding_table: dict[str, Handler] = {
'pythonapp': PythonappHandler(),
'modelspace': ModelSpaceHandler(),
}
self.handle_table: dict = {
'tell': self._tell
}
def _tell(self, command: list[str], pos = 0):
command_str = ''
for word in command[pos:]: command_str += word + ' '
print(command_str)
self.succeed = True
def _exit(self, command: list[str], pos = 0):
raise KeyboardInterrupt

View File

@@ -0,0 +1,18 @@
from shell.Handlers.ABS import Handler, global_repo
class ModelSpaceHandler(Handler):
def __init__(self):
super().__init__()
self.forwarding_table: dict[str, Handler] = {
}
self.handle_table: dict = {
'create_inter': self._create_inter
}
pass
def _create_inter(self, command: list[str], pos=0):
global_repo.add_model_package_interactive()
self.succeed = True

View File

@@ -0,0 +1,88 @@
from shell.Handlers.ABS import Handler
from pythonapp.Instance.Instance import Instance
class PythonappHandler(Handler):
def __init__(self):
super().__init__()
self.forwarding_table: dict[str, Handler] = {
'package': PackageHandler(self)
}
self.handle_table: dict = {
'create': self._create,
'load': self._load,
'show': self._show,
'activate': self._activate,
}
self._loaded_instances: dict[str, Instance] = {}
self._active_instance: Instance | None = None
def _load(self, command: list[str], pos = 0):
keys, args = self.parse_arguments(command[pos:], ['path', 'name'])
self._check_arg(keys, 'path')
if not keys['name']: keys['name'] = 'app'
i = Instance(path=str(keys['path']))
if not i.config.created: raise self.execution_error("ACTIVATE INSTANCE: instance not exists")
self._loaded_instances[keys['name']] = i
self._active_instance = i
print(f"instance {keys['path']} loaded and activated. identified by {keys['name']}")
def _activate(self, command: list[str], pos = 0):
keys, args = self.parse_arguments(command[pos:], ['name'])
self._check_arg(keys, 'name')
i = self._loaded_instances.get(command[1], None)
if i:
self._active_instance = i
else:
raise ValueError(f"pyapp {keys['name']} not loaded")
def _show(self, command: list[str], pos = 0):
print("Environment type:", self._active_instance.config.env_type)
# TODO Add new config info (app section)
def _create(self, command: list[str], pos = 0):
keys, args = self.parse_arguments(command[pos:], ['env', 'path', 'python'])
self.succeed = True
pass
@property
def active_instance(self):
return self._active_instance
class PackageHandler(Handler):
def __init__(self,parent: PythonappHandler):
super().__init__()
self.forwarding_table: dict[str, Handler] = {}
self.handle_table: dict = {
'install': self._install,
'exclude': self._exclude
}
self.parent = parent
def _install(self, command: list[str], pos = 0):
if not self.parent.active_instance: raise self.execution_error("I don't have active instance yet!")
keys, args = self.parse_arguments(command[pos:], ['pin', 'index', 'extra'], key_mode=True)
if keys['pin']: pin = True
else: pin = False
self.parent.active_instance.install_packages(pkgs=args, repo=keys.get('index', None), extra=keys.get('extra', None), pin=pin)
def _exclude(self, command: list[str], pos = 0):
if not self.parent.active_instance: raise self.execution_error("I don't have active instance yet!")
pkgs = command[pos:]
self.parent.active_instance.exclude_packages(pkgs)
self.succeed = True

View File

Binary file not shown.

Binary file not shown.

96
shell/Interactive.py Normal file
View File

@@ -0,0 +1,96 @@
import sys
from colorama import init, Fore, Style
from shell.Handlers.GlobalHandler import GlobalHandler
from shell.Parser import Parser
# Инициализация colorama для кроссплатформенной поддержки цветов
init()
class Interactive:
def __init__(self):
self.prompt = Interactive._get_colored_prompt()
self.parser = Parser()
self.handler = GlobalHandler()
@classmethod
def _get_colored_prompt(cls, prompt = "Vaiola> "):
"""Создает градиентный цветной промпт 'Vaiola>'"""
colored_prompt = ""
# Цвета для градиента (от ярко-белого к фиолетовому и обратно к белому)
colors = [
Fore.LIGHTWHITE_EX, # V
Fore.MAGENTA, # a
Fore.MAGENTA, # i (немного менее яркий фиолетовый)
Fore.LIGHTMAGENTA_EX, # o (фиолетовый)
Fore.LIGHTMAGENTA_EX, # l (немного более яркий фиолетовый)
Fore.LIGHTWHITE_EX, # a (ярко-фиолетовый)
Fore.LIGHTWHITE_EX # > (ярко-белый)
]
# Применяем цвета к каждому символу промпта
for i, char in enumerate(prompt):
if i < len(colors):
colored_prompt += colors[i] + char
else:
colored_prompt += Fore.LIGHTWHITE_EX + char
colored_prompt += Style.RESET_ALL
return colored_prompt
def input(self):
args = ['']
while True:
new_args = self.parser.parse(input(self.prompt).strip())
if len(new_args) == 0:
continue
args[len(args) - 1] += '\n' + new_args[0] if args[len(args) - 1] != '' else new_args[0]
args.extend(new_args[1:])
if self.parser.in_quotes:
self.prompt = self._get_colored_prompt("\"____\"> ")
continue
else:
self.prompt = self._get_colored_prompt()
break
return args
def start(self):
"""Запускает интерактивную оболочку"""
while True:
try:
command = self.input()
try:
self.handler.handle(command)
except ValueError as e:
print(f"Error: {e}")
except RuntimeWarning:
print("Warning: last command failed")
# # Выход из цикла по команде exit или quit
# if command.lower() in ['exit', 'quit']:
# print("До свидания!")
# break
# # Парсим команду
# try:
# parsed = Parser.parse(command)
# print(f"Парсинг результата: {parsed}")
# # Здесь можно добавить обработку команд
# except ValueError as e:
# print(f"Ошибка: {e}")
except KeyboardInterrupt:
print("\nGoodbye!")
break
except EOFError:
print("\nGoodbye!")
break
# Запуск интерактивной оболочки
if __name__ == "__main__":
Interactive().start()

42
shell/Parser.py Normal file
View File

@@ -0,0 +1,42 @@
class Parser:
def __init__(self):
self.in_quotes = False
def parse(self, command: str) -> list[str]:
tokens = []
current_token = []
for char in command:
if char == '"':
if self.in_quotes:
# Завершаем токен внутри кавычек
tokens.append(''.join(current_token))
current_token = []
self.in_quotes = False
else:
# Начинаем новый токен в кавычках
if current_token:
# Если до кавычек были символы, добавляем их как отдельный токен
tokens.append(''.join(current_token))
current_token = []
self.in_quotes = True
elif char == ' ':
if self.in_quotes:
# Внутри кавычек пробелы добавляем к текущему токену
current_token.append(char)
else:
if current_token:
# Завершаем текущий токен, если он есть
tokens.append(''.join(current_token))
current_token = []
else:
# Любой символ, кроме кавычек и пробела
current_token.append(char)
if current_token:
tokens.append(''.join(current_token))
return tokens

0
shell/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.