diff --git a/main.py b/main.py new file mode 100644 index 00000000..3e323b9c --- /dev/null +++ b/main.py @@ -0,0 +1,16 @@ +"""该入口文件仅在nb run无法正常工作时使用 +""" + +import nonebot +from nonebot import get_driver +from nonebot.adapters.onebot.v11 import Adapter +from nonebot.plugin import load_plugin + +nonebot.init() +load_plugin("nonebot_plugin_marshoai") + +driver = get_driver() +driver.register_adapter(Adapter) + +if __name__ == "__main__": + nonebot.run() diff --git a/nonebot_plugin_marshoai/config.py b/nonebot_plugin_marshoai/config.py index 5516640f..c4c1239c 100755 --- a/nonebot_plugin_marshoai/config.py +++ b/nonebot_plugin_marshoai/config.py @@ -49,6 +49,9 @@ class ConfigModel(BaseModel): marshoai_tencent_secretkey: str | None = None marshoai_plugin_dirs: list[str] = [] + """插件目录(不是工具)""" + marshoai_devmode: bool = False + """开发者模式""" yaml = YAML() diff --git a/nonebot_plugin_marshoai/deal_latex.py b/nonebot_plugin_marshoai/deal_latex.py index 2b8ba91a..873e6480 100755 --- a/nonebot_plugin_marshoai/deal_latex.py +++ b/nonebot_plugin_marshoai/deal_latex.py @@ -204,7 +204,6 @@ class JRTChannel(ConvertChannel): "outputScale": "{}%".format(dpi / 3 * 5), }, ) - print(post_response) if post_response.status_code == 200: if not (json_response := post_response.json())["error"]: diff --git a/nonebot_plugin_marshoai/dev.py b/nonebot_plugin_marshoai/dev.py new file mode 100644 index 00000000..7af58bba --- /dev/null +++ b/nonebot_plugin_marshoai/dev.py @@ -0,0 +1,7 @@ +from nonebot import require + +require("nonebot_plugin_alconna") + +from nonebot_plugin_alconna import Alconna, on_alconna + +function_call = on_alconna("marshocall") diff --git a/nonebot_plugin_marshoai/plugin/docstring/__init__.py b/nonebot_plugin_marshoai/plugin/docstring/__init__.py new file mode 100644 index 00000000..06a29aac --- /dev/null +++ b/nonebot_plugin_marshoai/plugin/docstring/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/28 下午1:46 +@Author : snowykami +@Email : snowykami@outlook.com +@File : __init__.py.py +@Software: PyCharm +""" diff --git a/nonebot_plugin_marshoai/plugin/docstring/docstring.py b/nonebot_plugin_marshoai/plugin/docstring/docstring.py new file mode 100644 index 00000000..e09ec768 --- /dev/null +++ b/nonebot_plugin_marshoai/plugin/docstring/docstring.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/28 下午1:46 +@Author : snowykami +@Email : snowykami@outlook.com +@File : docstring.py +@Software: PyCharm +""" +from typing import Optional + +from pydantic import BaseModel + + +class Attr(BaseModel): + name: str + type: str = "" + desc: str = "" + + +class Args(BaseModel): + name: str + type: str = "" + desc: str = "" + + +class Return(BaseModel): + desc: str = "" + + +class Exception_(BaseModel): + name: str + desc: str = "" + + +class Raise(BaseModel): + exceptions: list[Exception_] = [] + + +class Example(BaseModel): + desc: str = "" + input: str = "" + output: str = "" + + +class Docstring(BaseModel): + raw: str = "" + desc: str = "" + args: list[Args] = [] + attrs: list[Attr] = [] + return_: Optional[Return] = None + raise_: list[Exception_] = [] + example: Optional[str] = None + + front_matter: Optional[dict[str, str]] = None + + is_module: bool = False + + def add_desc(self, desc: str): + if self.desc == "": + self.desc = desc + else: + self.desc += "\n" + desc + + def add_arg(self, name: str, type_: str = "", desc: str = ""): + self.args.append(Args(name=name, type=type_, desc=desc)) + + def add_attrs(self, name: str, type_: str = "", desc: str = ""): + self.attrs.append(Attr(name=name, type=type_, desc=desc)) + + def add_return(self, desc: str = ""): + self.return_ = Return(desc=desc) + + def add_raise(self, name: str, desc: str = ""): + self.raise_.append(Exception_(name=name, desc=desc)) + + def add_example(self, desc: str = ""): + if self.example is None: + self.example = desc + else: + self.example += "\n" + desc + + def add_front_matter(self, key: str, value: str): + if self.front_matter is None: + self.front_matter = {} + self.front_matter[key] = value + + def reduction(self, style: str = "google") -> str: + """ + 通过解析结果还原docstring + Args: + style: docstring风格 + Returns: + + """ + ret = "" + if style == "google": + ret += self.desc + "\n" + if self.args: + ret += "Args:\n" + for arg in self.args: + ret += f" {arg.name}: {arg.type}\n {arg.desc}\n" + if self.attrs: + ret += "Attributes:\n" + for attr in self.attrs: + ret += f" {attr.name}: {attr.type}\n {attr.desc}\n" + if self.return_: + ret += "Returns:\n" + ret += f" {self.return_.desc}\n" + + if self.raise_: + ret += "Raises:\n" + for exception in self.raise_: + ret += f" {exception.name}\n {exception.desc}\n" + + if self.example: + ret += "Examples:\n" + ret += f" {self.example}\n" + return ret + + def __str__(self): + return self.desc diff --git a/nonebot_plugin_marshoai/plugin/docstring/parser.py b/nonebot_plugin_marshoai/plugin/docstring/parser.py new file mode 100644 index 00000000..11073b67 --- /dev/null +++ b/nonebot_plugin_marshoai/plugin/docstring/parser.py @@ -0,0 +1,222 @@ +""" +Google docstring parser for Python. +""" + +from typing import Optional + +from .docstring import Docstring + +placeholder = { + "%20": " ", + "%3A": ":", +} + + +def reduction(s: str) -> str: + """还原特殊字符""" + for k, v in placeholder.items(): + s = s.replace(k, v) + return s + + +pre_handle_ph = { + "://": "%3A//", +} + + +def pre_handle(s: str) -> str: + """特殊字符保护""" + for k, v in pre_handle_ph.items(): + s = s.replace(k, v) + return s + + +class Parser: ... + + +class GoogleDocstringParser(Parser): + _tokens = { + "Args": "args", + "Arguments": "args", + "参数": "args", + "Return": "return", + "Returns": "return", + "返回": "return", + "Attribute": "attribute", + "Attributes": "attribute", + "属性": "attribute", + "Raises": "raises", + "Raise": "raises", + "引发": "raises", + "Example": "example", + "Examples": "example", + "示例": "example", + "Yields": "yields", + "Yield": "yields", + "产出": "yields", + "Requires": "requires", + "Require": "requires", + "需要": "requires", + "FrontMatter": "front_matter", + "前言": "front_matter", + } + + def __init__(self, docstring: str, indent: int = 4, **kwargs): + self.lines = pre_handle(docstring).splitlines() + self.indent = indent + self.lineno = 0 # Current line number + self.char = 0 # Current character position + + self.is_module = kwargs.get("is_module", False) + """是否为模块的docstring,是则不在说明处添加说明字样""" + self.docstring = Docstring(raw=docstring, **kwargs) + + def read_line(self, move: bool = True) -> str: + """ + 每次读取一行 + Args: + move: 是否移动指针 + Returns: + """ + if self.lineno >= len(self.lines): + return "" + line = self.lines[self.lineno] + if move: + self.lineno += 1 + return line + + def match_token(self) -> Optional[str]: + """ + 解析下一行的token + Returns: + + """ + for token in self._tokens: + line = self.read_line(move=False) + if line.strip().startswith(token): + self.lineno += 1 + return self._tokens[token] + return None + + def parse_args(self): + """ + 依次解析后面的参数行,直到缩进小于等于当前行的缩进 + """ + while line := self.match_next_line(): + if ":" in line: + name, desc = line.split(":", 1) + self.docstring.add_arg(reduction(name.strip()), reduction(desc.strip())) + else: + self.docstring.add_arg(reduction(line.strip())) + + def parse_return(self): + """ + 解析返回值行 + """ + if line := self.match_next_line(): + self.docstring.add_return(reduction(line.strip())) + + def parse_raises(self): + """ + 解析异常行 + """ + while line := self.match_next_line(): + if ":" in line: + name, desc = line.split(":", 1) + self.docstring.add_raise( + reduction(name.strip()), reduction(desc.strip()) + ) + else: + self.docstring.add_raise(reduction(line.strip())) + + def parse_example(self): + """ + 解析示例行 + """ + while line := self.match_next_line(): + self.docstring.add_example( + reduction(line if line.startswith(" ") else line.strip()) + ) + + def parse_attrs(self): + """ + 解析属性行 + """ + while line := self.match_next_line(): + if ":" in line: + name, desc = line.split(":", 1) + self.docstring.add_attrs( + reduction(name.strip()), reduction(desc.strip()) + ) + else: + self.docstring.add_attrs(reduction(line.strip())) + + def match_next_line(self) -> Optional[str]: + """ + 在一个子解析器中,解析下一行,直到缩进小于等于当前行的缩进 + Returns: + """ + line = self.read_line(move=False) + if line.startswith(" " * self.indent): + self.lineno += 1 + return line[self.indent :] + else: + return None + + def parse(self) -> Docstring: + """ + 逐行解析,直到遇到token,解析token对应的内容, + + 最开始未解析到的内容全部加入desc + + Returns: + Docstring + """ + add_desc = True + add_front_matter = False + while self.lineno < len(self.lines): + token = self.match_token() + + if token is None and add_desc: + if self.is_module and self.lines[self.lineno].strip() == "---": + add_front_matter = not add_front_matter + self.lineno += 1 + continue + if add_front_matter and ":" in self.lines[self.lineno]: + key, value = map(str.strip, self.lines[self.lineno].split(":", 1)) + self.docstring.add_front_matter(key, value) + else: + self.docstring.add_desc(reduction(self.lines[self.lineno].strip())) + if token is not None: + add_desc = False + + match token: + case "args": + self.parse_args() + case "return": + self.parse_return() + case "attribute": + self.parse_attrs() + case "raises": + self.parse_raises() + case "example": + self.parse_example() + case _: + self.lineno += 1 + + return self.docstring + + +class NumpyDocstringParser(Parser): ... + + +class ReStructuredParser(Parser): ... + + +def parse( + docstring: str, parser: str = "google", indent: int = 4, **kwargs +) -> Docstring: + if parser == "google": + return GoogleDocstringParser(docstring, indent, **kwargs).parse() + else: + raise ValueError(f"Unknown parser: {parser}") diff --git a/nonebot_plugin_marshoai/plugin/register.py b/nonebot_plugin_marshoai/plugin/register.py index 90d79c69..76e34b06 100755 --- a/nonebot_plugin_marshoai/plugin/register.py +++ b/nonebot_plugin_marshoai/plugin/register.py @@ -3,6 +3,7 @@ import inspect +import litedoc from nonebot import logger from nonebot_plugin_marshoai.plugin.utils import is_coroutine_callable @@ -42,21 +43,24 @@ def function_call(*funcs: FUNCTION_CALL_FUNC) -> None: Returns: str: 函数定义信息 """ - # for func in funcs: - # function_call = get_function_info(func) - # # TODO: 注册函数 + for func in funcs: + function_call = get_function_info(func) + # TODO: 注册函数 -# def get_function_info(func: FUNCTION_CALL_FUNC) -> FunctionCall: -# """获取函数信息 +def get_function_info(func: FUNCTION_CALL_FUNC): + """获取函数信息 -# Args: -# func: 函数对象 + Args: + func: 函数对象 -# Returns: -# FunctionCall: 函数信息对象模型 -# """ -# description = func.__doc__ -# # TODO: 获取函数参数信息 -# parameters = {} -# pass + Returns: + FunctionCall: 函数信息对象模型 + """ + description = func.__doc__ + # TODO: 获取函数参数信息 + parameters = {} # type: ignore + # 使用inspect解析函数的传参及类型 + sig = inspect.signature(func) + for name, param in sig.parameters.items(): + logger.debug(name, param) diff --git a/nonebot_plugin_marshoai/plugin/utils.py b/nonebot_plugin_marshoai/plugin/utils.py index 030c4fea..55dd7e1c 100755 --- a/nonebot_plugin_marshoai/plugin/utils.py +++ b/nonebot_plugin_marshoai/plugin/utils.py @@ -32,3 +32,7 @@ def is_coroutine_callable(call: Callable[..., Any]) -> bool: return False func_ = getattr(call, "__call__", None) return inspect.iscoroutinefunction(func_) + + +def parse_function_docsring(): + pass