From 0e8be9de58454de079d772dec6c0ef9c1774a775 Mon Sep 17 00:00:00 2001 From: 苏向夜 Date: Fri, 26 Jan 2024 13:56:24 +0800 Subject: :recycle: refactor(infini): refacted infini as version 2.0.0-alpha.1 --- src/infini/__init__.py | 28 -------- src/infini/const.py | 3 + src/infini/core.py | 39 +++++++++++ src/infini/event.py | 98 ---------------------------- src/infini/exceptions.py | 43 +----------- src/infini/generator.py | 2 + src/infini/handler.py | 62 ++++++++---------- src/infini/input.py | 21 ++++++ src/infini/interceptor.py | 35 ++++++++++ src/infini/internal.py | 18 +++++ src/infini/logging.py | 33 ---------- src/infini/matcher.py | 34 ---------- src/infini/output.py | 19 ++++++ src/infini/queue.py | 47 +++++++++++++ src/infini/register.py | 163 ---------------------------------------------- src/infini/router.py | 21 ++++++ src/infini/settings.py | 1 - src/infini/typing.py | 28 ++++---- src/infini/utils/cli.py | 28 -------- src/infini/utils/gui.py | 5 -- 20 files changed, 248 insertions(+), 480 deletions(-) create mode 100644 src/infini/const.py create mode 100644 src/infini/core.py delete mode 100644 src/infini/event.py create mode 100644 src/infini/generator.py create mode 100644 src/infini/input.py create mode 100644 src/infini/interceptor.py create mode 100644 src/infini/internal.py delete mode 100644 src/infini/logging.py delete mode 100644 src/infini/matcher.py create mode 100644 src/infini/output.py create mode 100644 src/infini/queue.py delete mode 100644 src/infini/register.py create mode 100644 src/infini/router.py delete mode 100644 src/infini/settings.py delete mode 100644 src/infini/utils/cli.py delete mode 100644 src/infini/utils/gui.py (limited to 'src') diff --git a/src/infini/__init__.py b/src/infini/__init__.py index af9b8fa1..e69de29b 100644 --- a/src/infini/__init__.py +++ b/src/infini/__init__.py @@ -1,28 +0,0 @@ -"""Infini - -Core of HydroRoll, python version of GRPS-1. - - -本模块从子模块导入了以下内容: -- `Handler` => [`infini.handler`](./handler#Handler) -- `MessageEvent` => [`infini.event.MessageEvent`](./event#MessageEvent) -- `MatcherEvent` => [`infini.event.MatcherEvent`](./event#MatcherEvent) -- `WorkflowEvent` => [`infini.event.WorkflowEvent`](./event#WorkflowEvent) -- `Matcher` => [`infini.matcher.Matcher`](./matcher/#Matcher) -- `Register` => [`infini.register.Register`](./register#Register) -""" - - -from infini.handler import Handler -from infini.event import MessageEvent, MatcherEvent, WorkflowEvent -from infini.matcher import Matcher -from infini.register import Register - -__all__ = [ - "Handler", - "MessageEvent", - "Matcher", - "Register", - "MatcherEvent", - "WorkflowEvent", -] diff --git a/src/infini/const.py b/src/infini/const.py new file mode 100644 index 00000000..2ec42488 --- /dev/null +++ b/src/infini/const.py @@ -0,0 +1,3 @@ +from pathlib import Path + +SRC_HOME = Path.home() / ".ipm" / "src" \ No newline at end of file diff --git a/src/infini/core.py b/src/infini/core.py new file mode 100644 index 00000000..2bb3e7da --- /dev/null +++ b/src/infini/core.py @@ -0,0 +1,39 @@ +from infini.input import Input +from infini.interceptor import Interceptor +from infini.generator import Generator +from infini.handler import Handler +from infini.output import Output + + +class Core: + pre_interceptor: Interceptor + handler: Handler + generator: Generator + interceptor: Interceptor + + def input(self, input: Input): + if isinstance(pre_intercepted_stream := self.pre_intercept(input), Output): + yield self.generate(pre_intercepted_stream) + return + for handled_stream in self.handle(pre_intercepted_stream): + if handled_stream.is_empty(): + return # TODO 处理未找到 Handler + yield self.intercept(self.generate(handled_stream)) + + def pre_intercept(self, input: Input) -> Input | Output: + return self.pre_interceptor.input(input) + + def handle(self, input: Input): + iterator = self.handler.input(input) + for output in iterator: + yield output + + def generate(self, output: Output) -> str: + return output.name # TODO 生成器实现 + + def intercept(self, output: str) -> str: + return ( + self.generate(callback) + if isinstance(callback := self.interceptor.output(output), Output) + else callback + ) diff --git a/src/infini/event.py b/src/infini/event.py deleted file mode 100644 index 5ef9444e..00000000 --- a/src/infini/event.py +++ /dev/null @@ -1,98 +0,0 @@ -"""Infini 事件模块 - -此模块定义了 Infini 的事件类,包括 InfiniEvent, MessageEvent, WorkflowEvent 和 MatcherEvent。 -这些事件类用于在 Infini 框架中处理各种事件。 -""" - -from abc import ABCMeta, abstractmethod -from .typing import Dict, Any - -__all__ = [ - "InfiniEvent", - "MessageEvent", - "WorkflowEvent", - "MatcherEvent", -] - - -class InfiniEvent(metaclass=ABCMeta): - """Infini 事件基类""" - - name: str - kwargs: Dict[str, Any] - - @abstractmethod - def __repr__(self) -> str: - raise NotImplementedError - - def get_event_name(self) -> str: - return self.name - - -class MessageEvent(InfiniEvent): - """Message 事件""" - - name: str - output: str - kwargs: Dict[str, Any] - - def __init__(self, name: str, **kwargs) -> None: - self.name = name - self.kwargs = kwargs - - def __str__(self) -> str: - return f"" - - def __repr__(self) -> str: - return self.__str__() - - -class WorkflowEvent(InfiniEvent): - """Workflow 事件""" - - name: str - kwargs: Dict[str, Any] - - def __init__(self, name: str, **kwargs) -> None: - self.name = name - self.kwargs = kwargs - - def __repr__(self) -> str: - return f"" - - def __eq__(self, __value: object) -> bool: - if __value is str: - return self.name == __value - if isinstance(__value, WorkflowEvent): - return self.name == __value.name and self.kwargs == __value.kwargs - return False - - -class MatcherEvent(InfiniEvent): - """Matcher 事件""" - - name: str - prefix: str - string: str - kwargs: Dict[str, Any] - - def __init__( - self, - event_name: str, - prefix: str | None = None, - string: str | None = None, - **kwargs, - ): - self.name = event_name - self.prefix = prefix or "" - self.string = string or "" - self.kwargs = kwargs - - def __repr__(self) -> str: - return f"" - - def get_prefix(self): - return self.prefix - - def get_plain_text(self): - return self.string diff --git a/src/infini/exceptions.py b/src/infini/exceptions.py index 2038efcd..cb0a9e41 100644 --- a/src/infini/exceptions.py +++ b/src/infini/exceptions.py @@ -1,45 +1,6 @@ -"""Infini 异常处理模块 - -此模块定义了 Infini 项目中所有的自定义异常类。 -规则包作者后续实现的每个异常类都应该继承自 InfiniException。 -""" - - class InfiniException(Exception): """Infini 异常基类""" -class RulePackageException(InfiniException): - """由规则包抛出的异常基类, 所有规则包抛出的异常都应该继承此类。""" - - -class LoadError(InfiniException): - """加载规则包错误, 找不到指定的规则包或事件声明有误导致运行时错误时抛出。""" - - -class PackageNotFound(LoadError): - """规则包不存在时错误""" - - -class EventLoadError(LoadError, RuntimeError): - """事件声明导入失败""" - - -class HandlerLoadError(LoadError, RuntimeError): - """业务函数导入失败""" - - -class EventException(InfiniException): - """事件异常基类""" - - -class UnknownMatcherEvent(EventException): - """未知的给入实现""" - - -class UnknownMessageEvent(EventException): - """未知的给出实现""" - - -class UnsupportedError(EventException, RuntimeError): - """方法未被支持""" +class KeyError(InfiniException): + """键值错误""" diff --git a/src/infini/generator.py b/src/infini/generator.py new file mode 100644 index 00000000..a4d60e27 --- /dev/null +++ b/src/infini/generator.py @@ -0,0 +1,2 @@ +class Generator: + ... diff --git a/src/infini/handler.py b/src/infini/handler.py index 0f72eccf..ae3561f8 100644 --- a/src/infini/handler.py +++ b/src/infini/handler.py @@ -1,34 +1,28 @@ -"""Infini Handler - -此类是所有规则包业务类的基类。 -每个规则包业务类都需要定义一个名为 process 的抽象方法, 用于接收和处理传入的 MatcherEvent 事件,并返回一个 InfiniEvent 事件。 -此外,每个规则包业务类还可以定义一个名为 priority 的类属性,用于指定该业务类的优先级。优先级越高,该业务类处理事件的顺序越靠前。 -""" - -from abc import ABC, abstractmethod -from enum import Enum -from .typing import StateT, ClassVar, Generic -from .event import MatcherEvent, InfiniEvent - -__all__ = ["Handler", "HandlerLoadType"] - - -class HandlerLoadType(Enum): - """规则包加载类型。""" - - DIR = "dir" - NAME = "name" - FILE = "file" - CLASS = "class" - - -class Handler(ABC, Generic[StateT]): - """规则包业务基类""" - - name: str - priority: ClassVar[int] = 0 - block: ClassVar[bool] = False - - @abstractmethod - def process(self, event: MatcherEvent) -> InfiniEvent: - raise NotImplementedError +from infini.input import Input +from infini.output import Output +from infini.typing import List, RouterType, Callable +from infini.queue import EventQueue + + +class Handler: + handlers: List[RouterType] + + def input(self, input: Input): + queue = self.match(input.get_plain_text()) + if queue.is_empty(): + yield Output.empty() + return + while not queue.is_empty(): + output = queue.pop()(input) + yield output + if output.block: + return + + def match(self, text: str) -> EventQueue[Callable[[Input], Output]]: + queue = EventQueue() + + for handler in self.handlers: + if handler["router"].match(text): + queue.push(handler["priority"], handler["handler"]) + + return queue diff --git a/src/infini/input.py b/src/infini/input.py new file mode 100644 index 00000000..21c12a2f --- /dev/null +++ b/src/infini/input.py @@ -0,0 +1,21 @@ +from infini.typing import Dict, Any, Generic, T + + +class Input(Generic[T]): + plain_data: T + variables: Dict[str, Any] + + def __init__(self, plain_data: Any, variables: Dict[str, Any] | None = None) -> None: + self.plain_data = plain_data + self.variables = variables or {} + + def get_session_id(self) -> str: + if session_id := self.variables.get("session_id"): + return session_id + + user_id = self.variables.get("user_id", "unknown") + group_id = self.variables.get("group_id", "unknown") + return f"session_{group_id}_{user_id}" + + def get_plain_text(self) -> str: + return str(self.plain_data) diff --git a/src/infini/interceptor.py b/src/infini/interceptor.py new file mode 100644 index 00000000..54387281 --- /dev/null +++ b/src/infini/interceptor.py @@ -0,0 +1,35 @@ +from infini.input import Input +from infini.output import Output +from infini.typing import List, RouterType, Callable, Generic, T, overload +from infini.queue import EventQueue + + +class Interceptor: + interceptors: List[RouterType] + + def input(self, input: Input) -> Input | Output: + queue = self.match(input.get_plain_text()) + while not queue.is_empty(): + if isinstance(intercepted := queue.pop()(input), Output): + return intercepted # TODO 允许拦截器产出文本 + else: + input = intercepted + return input + + def output(self, output_text: str) -> str | Output: + queue = self.match(output_text) + while not queue.is_empty(): + if isinstance(intercepted := queue.pop()(input), Output): + return intercepted + else: + input = intercepted + return output_text + + def match(self, text: str) -> EventQueue[Callable[[Input], Input | Output]]: + queue = EventQueue() + + for interceptor in self.interceptors: + if interceptor["router"].match(text): + queue.push(interceptor["priority"], interceptor["handler"]) + + return queue diff --git a/src/infini/internal.py b/src/infini/internal.py new file mode 100644 index 00000000..4f800297 --- /dev/null +++ b/src/infini/internal.py @@ -0,0 +1,18 @@ +from infini.typing import List, ModuleType +from infini.const import SRC_HOME + +import importlib +import sys + + +def require(name: str, paths: List | None = None) -> ModuleType: + paths = [ + str(path) + for path in ( + (list(paths) + [str(SRC_HOME / name)]) if paths else [str(SRC_HOME / name)] + ) + ] + sys.path.extend(paths) + module = importlib.import_module(name) + (sys.path.remove(path) for path in paths) + return module diff --git a/src/infini/logging.py b/src/infini/logging.py deleted file mode 100644 index 979fee7d..00000000 --- a/src/infini/logging.py +++ /dev/null @@ -1,33 +0,0 @@ -"""Infini 日志。 - -Infini 使用 [loguru](https://github.com/Delgan/loguru) 来记录日志信息。 -自定义 logger 请参考 [loguru](https://github.com/Delgan/loguru) 文档。 -""" -from datetime import datetime -from multilogging import multilogger -from pathlib import Path -from .settings import DEBUG - - -__all__ = ["logger", "error_or_exception"] - -logger = multilogger(name="Infini", payload="Core", - level="DEBUG" if DEBUG else "INFO") -CURRENT_PATH = Path(__file__).resolve().parent -DATA_PATH = Path.home() / ".infini" -LOG_PATH = DATA_PATH / "logs" -if not LOG_PATH.exists(): - LOG_PATH.mkdir(parents=True, exist_ok=True) -logger.add( - sink=LOG_PATH / (datetime.now().strftime("%Y-%m-%d") + ".log"), - level="INFO", - rotation="10 MB", -) # 每个日志文件最大为 10MB - - -def error_or_exception(message: str, exception: Exception, verbose: bool = True): - if verbose: - logger.exception(exception) - logger.critical(message) - else: - logger.critical(f"{message} {exception!r}") diff --git a/src/infini/matcher.py b/src/infini/matcher.py deleted file mode 100644 index 8c11ff29..00000000 --- a/src/infini/matcher.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Infini Matcher - -用于处理和匹配事件。 -""" - -from .register import Register, register -from .event import MatcherEvent -from .handler import Handler -from .exceptions import UnknownMatcherEvent - - -class Matcher: - """事件处理单元""" - - register: Register - - def __init__(self, _register: Register | None = None) -> None: - self.register = _register or register - - def match(self, name: str) -> Handler: - if handler := self.register.handlers.match(name): - return handler if isinstance(handler, Handler) else handler() - else: - raise UnknownMatcherEvent(f"未知的规则包: {name}") - - def run(self, event: MatcherEvent) -> str: - callback_event = self.match(event.name).process(event) - return self.register.events.process( - callback_event.name, - **callback_event.kwargs or callback_event.kwargs, - ) - - -matcher = Matcher() diff --git a/src/infini/output.py b/src/infini/output.py new file mode 100644 index 00000000..ddb7f0a5 --- /dev/null +++ b/src/infini/output.py @@ -0,0 +1,19 @@ +from infini.typing import Literal + + +class Output: + type: Literal["null", "text", "workflow"] + name: str + status: int + block: bool + + @classmethod + def empty(cls) -> "Output": + output = cls() + output.type = "null" + output.status = 0 + output.block = True + return output + + def is_empty(self) -> bool: + return self.type == "null" diff --git a/src/infini/queue.py b/src/infini/queue.py new file mode 100644 index 00000000..9dd441ee --- /dev/null +++ b/src/infini/queue.py @@ -0,0 +1,47 @@ +from infini.exceptions import KeyError +from infini.typing import Generic, T +from collections import deque + +import heapq + + +class EventQueue(Generic[T]): + heap: list + entry_finder: dict + counter: int + order_queue: deque + + def __init__(self) -> None: + self.heap = [] + self.entry_finder = {} + self.counter = 0 + self.order_queue = deque() + + def push(self, priority: int, item: T) -> None: + if item in self.entry_finder: + self.remove(item) + entry = [priority, self.counter, item] + self.counter += 1 + self.entry_finder[item] = entry + heapq.heappush(self.heap, entry) + self.order_queue.appendleft(item) + + def pop(self) -> T: + while self.heap: + _, _, item = heapq.heappop(self.heap) + if self.entry_finder[item] is not None: + del self.entry_finder[item] + self.order_queue.remove(item) + return item + raise KeyError("事件队列为空.") + + def remove(self, item) -> None: + entry = self.entry_finder.pop(item) + entry[-1] = None + + def is_empty(self) -> bool: + return not bool(self.heap) + + def generate(self): + while not self.is_empty(): + yield self.pop() diff --git a/src/infini/register.py b/src/infini/register.py deleted file mode 100644 index 55677152..00000000 --- a/src/infini/register.py +++ /dev/null @@ -1,163 +0,0 @@ -"""Infini 注册模块 - -包含了 Infini 事件和处理器的注册和加载功能。 -""" - -from pathlib import Path -from .exceptions import UnknownMatcherEvent, UnsupportedError -from .handler import Handler -from .event import InfiniEvent, MessageEvent, WorkflowEvent -from .typing import Dict, Type -from .exceptions import LoadError, EventLoadError, HandlerLoadError - -import re -import sys -import importlib -import inspect - - -class Loader: - """Infini 事件加载器 - - Raises: - LoadError: 规则包导入错误 - EventLoadError: 规则包事件导入错误 - HandlerLoadError: 规则包业务函数导入错误 - """ - - name: str - meta_path: Path - events: dict - handlers: dict - - def __init__(self, meta_path: Path | str) -> None: - if isinstance(meta_path, str): - self.meta_path = Path(meta_path).resolve() - else: - self.meta_path = meta_path.resolve() - self.name = self.meta_path.name - self.events = {} - self.handlers = {} - - def load(self) -> None: - sys.path.append(str(self.meta_path)) - try: - importlib.import_module(f"{self.name}") - except Exception as error: - raise LoadError(f"规则包[{self.name}]导入失败: {error}") from error - - try: - event_module = importlib.import_module(f"{self.name}.event") - events = [ - cls[1] - for cls in inspect.getmembers(event_module, inspect.isclass) - if issubclass(cls[1], InfiniEvent) - and not cls[1].__module__.startswith("infini") - ] - for event in events: - self.events[f"{self.name}.{event.__dict__['name']}"] = event - except Exception as error: - raise EventLoadError(f"规则包[{self.name}]事件导入失败: {error}") from error - - try: - handler_module = importlib.import_module(f"{self.name}.handler") - handlers = [ - cls[1] - for cls in inspect.getmembers(handler_module, inspect.isclass) - if issubclass(cls[1], Handler) - and not cls[1].__module__.startswith("infini") - ] - for handler in handlers: - self.handlers[f"{self.name}.{handler.__dict__['name']}"] = handler - except Exception as error: - raise HandlerLoadError(f"规则包[{self.name}]业务函数导入失败: {error}") from error - - sys.path.remove(str(self.meta_path)) - - -class Events: - """规则包事件集合 - - Raises: - UnsupportedError: 事件尚未被支持 - UnknownMatcherEvent: 事件不存在 - - Returns: - _type_: _description_ - """ - - _events: Dict[str, Type[InfiniEvent]] - - def __init__(self) -> None: - self._events = {} - - def register(self, name: str, event: Type[InfiniEvent]) -> None: - self._events[name.lower()] = event - - def update(self, _events: dict) -> None: - self._events.update(_events) - - def _process(self, event: Type[InfiniEvent], **kwargs) -> str: - # sourcery skip: merge-duplicate-blocks - if issubclass(event, MessageEvent): - return self._format(event.__dict__["output"], **kwargs) - elif issubclass(event, WorkflowEvent): - # TODO: handle workflow events - raise UnsupportedError - else: - raise UnsupportedError - - def process(self, name: str, **kwargs) -> str: - if event := self._events.get(name.lower()): - return self._process(event, **kwargs) - raise UnknownMatcherEvent(f"事件[{name.lower()}]不存在!") - - def _format(self, string: str, **kwargs): - pattern = r"{(.*?)}" - values = re.findall(pattern, string) - for value in values: - kwarg = kwargs.get(value) - value = kwarg or "" - string = re.sub(pattern, value, string) - return string - - -class Handlers: - """规则包业务集合 - - Returns: - _type_: _description_ - """ - - _handlers: Dict[str, Handler] = {} - - def register(self, name: str, handler: Handler) -> None: - self._handlers[name.lower()] = handler - - def update(self, _events: dict) -> None: - self._handlers.update(_events) - - def match(self, name: str) -> Handler | None: - return self._handlers.get(name.lower()) - - -class Register: - """注册器""" - - events: Events - handlers: Handlers - - def __init__(self) -> None: - self.events = Events() - self.handlers = Handlers() - - def register( - self, meta_path: Path | str | None = None, loader: Type[Loader] | None = None - ): - _loader = Loader(meta_path or ".") if not loader else loader(meta_path or ".") - _loader.load() - self.events.update(_loader.events) - self.handlers.update(_loader.handlers) - - -register = Register() diff --git a/src/infini/router.py b/src/infini/router.py new file mode 100644 index 00000000..a520a456 --- /dev/null +++ b/src/infini/router.py @@ -0,0 +1,21 @@ +from infini.typing import overload + + +class Router: + sign: str + + def __init__(self, sign: str) -> None: + self.sign = sign + + def match(self, input: str) -> bool: + return self.sign == input.strip() + + +class StartswithRouter(Router): + def match(self, input: str) -> bool: + return input.strip().startswith(self.sign) + + +class ContainsRouter(Router): + def match(self, input: str) -> bool: + return self.sign in input.strip() diff --git a/src/infini/settings.py b/src/infini/settings.py deleted file mode 100644 index 51c64dd2..00000000 --- a/src/infini/settings.py +++ /dev/null @@ -1 +0,0 @@ -DEBUG = True diff --git a/src/infini/typing.py b/src/infini/typing.py index 0da72385..0a0d3292 100644 --- a/src/infini/typing.py +++ b/src/infini/typing.py @@ -1,24 +1,22 @@ -"""Infini 类型提示支持。 - -此模块定义了部分 Infini 使用的类型。 -""" - from typing import ( Dict as Dict, + List as List, Any as Any, - Type as Type, - ClassVar as ClassVar, Generic as Generic, - TYPE_CHECKING as TYPE_CHECKING, - TypeVar as TypeVar, Callable as Callable, - NoReturn as NoReturn, - Awaitable as Awaitable, + Literal as Literal, + overload as overload, + TypeVar, + TypedDict, + Union, ) +from types import ModuleType as ModuleType +from . import router, input, output -if TYPE_CHECKING: - from typing import Any +T = TypeVar("T") -__all__ = ["StateT"] -StateT = TypeVar("StateT") +class RouterType(TypedDict): + priority: int + router: router.Router + handler: Callable[["input.Input"], Union["input.Input", "output.Output"]] diff --git a/src/infini/utils/cli.py b/src/infini/utils/cli.py deleted file mode 100644 index 3d30f839..00000000 --- a/src/infini/utils/cli.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -Infini 终端命令解析模块 -""" - -import argparse -import sys - - -def parse_args(argv: list[str] | None = None) -> argparse.Namespace: - # sourcery skip: extract-duplicate-method - parser = argparse.ArgumentParser(prog="Infini CLI", description="Infini 命令行工具") - - parser.add_argument("--gui", action="store_true", help="启用 GUI 模式") - - subparsers = parser.add_subparsers(title="功能件", dest="operate") - - # 子命令 `new` - new_parser = subparsers.add_parser("new", help="创建一个 Infini 规则包模板") - new_parser.add_argument("path", help="目标位置") - new_parser.add_argument("-v", "--verbose", action="store_true", help="异常追踪") - new_parser.add_argument("-f", "--force", action="store_true", help="强制创建") - - # 子命令 `test` - test_parser = subparsers.add_parser("test", help="测试 Infini 规则包") - test_parser.add_argument("path", help="目标位置") - test_parser.add_argument("-v", "--verbose", action="store_true", help="异常追踪") - - return parser.parse_args(sys.argv[1:] or argv or ["--help"]) diff --git a/src/infini/utils/gui.py b/src/infini/utils/gui.py deleted file mode 100644 index 5e8c91c4..00000000 --- a/src/infini/utils/gui.py +++ /dev/null @@ -1,5 +0,0 @@ -""" -Infini 图形用户界面模块 -""" - -# TODO -- cgit v1.2.3-70-g09d2