添加开发模式配置;新增入口文件和函数调用处理逻辑;重构文档字符串解析类

This commit is contained in:
远野千束(神羽) 2024-12-15 01:06:41 +08:00
parent 52046ba032
commit 4083aba99f
9 changed files with 403 additions and 15 deletions

16
main.py Normal file
View File

@ -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()

View File

@ -49,6 +49,9 @@ class ConfigModel(BaseModel):
marshoai_tencent_secretkey: str | None = None
marshoai_plugin_dirs: list[str] = []
"""插件目录(不是工具)"""
marshoai_devmode: bool = False
"""开发者模式"""
yaml = YAML()

View File

@ -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"]:

View File

@ -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")

View File

@ -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
"""

View File

@ -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

View File

@ -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}")

View File

@ -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)

View File

@ -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