From b5b15c82f8e995d3d6ffe00bcbac999830cdaf43 Mon Sep 17 00:00:00 2001 From: snowykami Date: Tue, 17 Sep 2024 14:37:45 +0800 Subject: [PATCH] =?UTF-8?q?:package:=20docs:=20=E8=B5=84=E6=BA=90=E5=95=86?= =?UTF-8?q?=E5=BA=97=E6=96=B0=E5=A2=9E=E5=8F=91=E5=B8=83=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ISSUE_TEMPLATE.md | 44 --------- liteyuki_flow/__init__.py | 3 + liteyuki_flow/__main__.py | 60 ++++++++++++ liteyuki_flow/const.py | 15 +++ liteyuki_flow/markdown_parser.py | 136 ++++++++++++++++++++++++++++ liteyuki_flow/plugin_handler.py | 7 ++ liteyuki_flow/requirements.txt | 3 + liteyuki_flow/resource_handler.py | 131 +++++++++++++++++++++++++++ liteyuki_flow/ts.md | 26 ++++++ liteyuki_flow/typ.py | 20 ++++ 10 files changed, 401 insertions(+), 44 deletions(-) delete mode 100644 .github/workflows/ISSUE_TEMPLATE.md create mode 100644 liteyuki_flow/__init__.py create mode 100644 liteyuki_flow/__main__.py create mode 100644 liteyuki_flow/const.py create mode 100644 liteyuki_flow/markdown_parser.py create mode 100644 liteyuki_flow/plugin_handler.py create mode 100644 liteyuki_flow/requirements.txt create mode 100644 liteyuki_flow/resource_handler.py create mode 100644 liteyuki_flow/ts.md create mode 100644 liteyuki_flow/typ.py diff --git a/.github/workflows/ISSUE_TEMPLATE.md b/.github/workflows/ISSUE_TEMPLATE.md deleted file mode 100644 index b91c5019..00000000 --- a/.github/workflows/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,44 +0,0 @@ -# 问题反馈 - -## **请确保** - -- 已认真阅读[文档]("https://bot.liteyuki.icu"),该问题不是文档提及的或你自己操作不当造成的 -- 你的问题是在最新版本的代码上测试的 -- 请勿重复提交相同或类似的issue - - -## **描述问题** - -请在此简单描述问题 - - - -## **如何复现** - -请阐述一下如何重现这个问题 -### 预期 - -描述你期望发生的事情 - -### 实际 - -描述实际发生的事情 - - - -## **日志或截图** -``` -日志内容 -``` - - -## **设备信息** -- **系统**: [例如 Ubuntu 22.04] -- **CPU**: [例如 Intel i7-7700K] -- **内存**: [例如 16GB] -- **Python**: [例如CPython 3.10.7] - - -**补充内容** - -可选,推荐提供`pip freeze`的输出,以及其他相关信息,以及你的建议 diff --git a/liteyuki_flow/__init__.py b/liteyuki_flow/__init__.py new file mode 100644 index 00000000..a18a2574 --- /dev/null +++ b/liteyuki_flow/__init__.py @@ -0,0 +1,3 @@ +""" +Module docs +""" \ No newline at end of file diff --git a/liteyuki_flow/__main__.py b/liteyuki_flow/__main__.py new file mode 100644 index 00000000..c94ebe89 --- /dev/null +++ b/liteyuki_flow/__main__.py @@ -0,0 +1,60 @@ +""" +Module docs +""" +import os +from github import Github +from argparse import ArgumentParser + +from liteyuki_flow.const import PLUGIN_PREFIX, RESOURCE_PREFIX +from liteyuki_flow.typ import err, nil # type: ignore + +if __name__ == "__main__": + parser = ArgumentParser() + parser.add_argument("--handle", action="store_true") # 处理issue + + parser.add_argument("-p", "--parse", action="store_true") # 解析markdown文件 + parser.add_argument("-i", "--input", type=str, help="Path to the markdown file.") + args = parser.parse_args() + + if args.handle: + print("Starting the issue handler module...") + ISSUE_NUMBER = os.getenv("ISSUE_NUMBER") + REPOSITORY = os.getenv("REPOSITORY") + ACT_TYPE = os.getenv("ACT_TYPE") # opened, edited, closed, reopened + if ISSUE_NUMBER is None or REPOSITORY is None or ACT_TYPE is None: + raise ValueError("Issue number, repository and action type are required.") + + g = Github(os.getenv("GITHUB_TOKEN")) + repo = g.get_repo(REPOSITORY) + issue = g.get_repo(REPOSITORY).get_issue(int(ISSUE_NUMBER)) + + # 审资源 + if issue.title.strip().startswith(RESOURCE_PREFIX): + from resource_handler import handle_resource # type: ignore + handle_resource(github=g, issue=issue, repo=repo, act_type=ACT_TYPE) + + # 审插件 + elif issue.title.strip().startswith(PLUGIN_PREFIX): + from plugin_handler import handle_plugin # type: ignore + pass + + else: + print("No handler found for the issue.") + + elif args.parse: + print("Starting the markdown parser module...") + from .markdown_parser import MarkdownParser # type: ignore + + if args.input is None: + raise ValueError("Input file is required.") + with open(args.input, "r", encoding="utf-8") as f: + content = f.read() + + md_parser = MarkdownParser(content) # type: ignore + err = md_parser.parse_front_matters() # type: ignore + if err != nil: + print(f"Err: {err}") + for k, v in md_parser.front_matters.content.items(): + print(f"{k}: {v}") + else: + print("No module specified.") diff --git a/liteyuki_flow/const.py b/liteyuki_flow/const.py new file mode 100644 index 00000000..de919907 --- /dev/null +++ b/liteyuki_flow/const.py @@ -0,0 +1,15 @@ +""" +Module docs +""" + +OPENED = "opened" +EDITED = "edited" +CLOSED = "closed" +REOPENED = "reopened" +RESOURCE_PREFIX = "Resource: " +PLUGIN_PREFIX = "Plugin: " + +PLUGIN_JSON = "docs/publics/plugins.json" +RESOURCE_JSON = "docs/publics/resources.json" + +edit_tip = "若要修改请编辑这段front matter,不要编辑正文/If you want to modify, please edit the front matter, do not edit the body" diff --git a/liteyuki_flow/markdown_parser.py b/liteyuki_flow/markdown_parser.py new file mode 100644 index 00000000..1e77e44a --- /dev/null +++ b/liteyuki_flow/markdown_parser.py @@ -0,0 +1,136 @@ +""" +从markdown提取插件/资源信息 +""" +from typing import Any + +from liteyuki_flow.typ import Nil, err, nil # type: ignore + + + + +# # xxx +class Header: + def __init__(self, level: int, content: str): + self.level = level + self.content = content + + def __str__(self): + return f'Header({self.level}, {self.content})' + + def __repr__(self): + return self.__str__() + + +# - xxx +class List: + def __init__(self, level: int, content: str): + self.level = level + self.content = content + + def __str__(self): + return f'List({self.level}, {self.content})' + + def __repr__(self): + return self.__str__() + + +class FrontMatter: + def __init__(self, content: dict[str, str]): + self.content = content + + def __setitem__(self, key: str, value: str): + self.content[key] = value + + def get(self, key, default=None) -> Any: + return self.content.get(key, default) + + def __str__(self): + return "\n".join([f'{k}: {v}' for k, v in self.content.items()]) + + +class MarkdownParser: + def __init__(self, content: str): + self.content = content + self.content_lines = content.split('\n') + self.front_matters: FrontMatter = FrontMatter({}) + + self._content_list: list[Any] = [self.front_matters] + + self.lineno = 0 + + self._parsed = False + + def parse_front_matters(self) -> err: + if self.content_lines[self.lineno].strip() != '---': + return ValueError('Invalid front matter') + while self.lineno < len(self.content_lines): + self._next_line() + line = self.content_lines[self.lineno] + if line.strip() == '---': + break + if line.strip().startswith('#'): + # fm注释 + continue + try: + key, value = line.split(':', 1) + except ValueError: + return Exception(f'Invalid front matter: {line}') + self.front_matters[key.strip()] = value.strip() + return nil + + def _parse_content(self) -> tuple[list[Any], err]: + content: list[Any] = [] + while self.lineno < len(self.content_lines): + item, e = self._parse_line() + if e != nil: + return nil, e + content.append(item) + return content, nil + + def _parse_line(self) -> tuple[Any, err]: + line = self.content_lines[self.lineno] + if line.startswith('#'): + # 计算start有几个# + start = 0 + while line[start] == '#': + start += 1 + return Header(start, line[start:].strip()), nil + elif line.startswith('-'): + start = 0 + while line[start] == '-': + start += 1 + return List(start, line[start:].strip()), nil + + # 处理'): + self._next_line() + line = self.content_lines[self.lineno] + return None, nil + # 处理[//]: # (注释) continue + elif line.strip().startswith('[//]: #'): + self._next_line() + return None, nil + + self._next_line() + return nil, ValueError(f'Invalid line: {line}') + + def _next_line(self): + self.lineno += 1 + + def parse(self) -> tuple[list[Any] | Nil, err]: + if self._parsed: + return self._content_list, nil + + e = self.parse_front_matters() + if e != nil: + return nil, e + + ls, e = self._parse_content() + if e != nil: + return nil, e + + self._content_list.extend(ls) + self._parsed = True + + return self._content_list, nil diff --git a/liteyuki_flow/plugin_handler.py b/liteyuki_flow/plugin_handler.py new file mode 100644 index 00000000..f9dcb508 --- /dev/null +++ b/liteyuki_flow/plugin_handler.py @@ -0,0 +1,7 @@ +""" +Module docs +""" + + +def plugin_handler(): + pass \ No newline at end of file diff --git a/liteyuki_flow/requirements.txt b/liteyuki_flow/requirements.txt new file mode 100644 index 00000000..f1ac31e0 --- /dev/null +++ b/liteyuki_flow/requirements.txt @@ -0,0 +1,3 @@ +PyGithub==2.4.0 +requests==2.31.0 +pyyaml==6.0.2 \ No newline at end of file diff --git a/liteyuki_flow/resource_handler.py b/liteyuki_flow/resource_handler.py new file mode 100644 index 00000000..f9804493 --- /dev/null +++ b/liteyuki_flow/resource_handler.py @@ -0,0 +1,131 @@ +""" +Module docs +""" +import requests # type: ignore +import zipfile + +from github import Github, InputGitTreeElement, GitTree +from github.Issue import Issue +from github.Repository import Repository +import json +import yaml + +from liteyuki_flow.const import OPENED, EDITED, CLOSED, REOPENED, RESOURCE_JSON +from liteyuki_flow.markdown_parser import MarkdownParser +from liteyuki_flow.typ import err, nil + + +# opened: 创建新的资源包,预审核 +# edited: 编辑资源包信息,需重新审核 +# closed: 审核通过,修改json并提交 +# reopened: 重新打开,无操作 + +# opened | edited +def pre_check(github: Github, issue: Issue, repo: Repository) -> err: + parser = MarkdownParser(issue.body) + parser.parse_front_matters() + name = parser.front_matters.get("name") + desc = parser.front_matters.get("desc") + url = parser.front_matters.get("url") + homepage = parser.front_matters.get("homepage") # optional + author = parser.front_matters.get("author") + if not all((name, desc, url, author)): + issue.create_comment("Name, desc, url, homepage and author are required.") + return ValueError("Name, desc, url, homepage and author are required.") + + # 下载并解析资源包 + r = requests.get(url) + if r.status_code != 200: + issue.create_comment("Download failed.") + return ValueError("Download failed.") + + with open(f"tmp/{name}.zip", "wb") as f: + f.write(r.content) + # 解压 + with zipfile.ZipFile(f"tmp/{name}.zip", "r") as z: + z.extractall(f"tmp/{name}") + + # 检测包内metadata.yml文件 + try: + data = yaml.load(open(f"tmp/{name}/metadata.yml"), Loader=yaml.SafeLoader) + except Exception: + issue.create_comment("metadata.yml not found or invalid.") + return ValueError("metadata.yml not found or invalid.") + + # 检测必要字段 name,description,version + if not all((data.get("name"), data.get("description"), data.get("version"))): + issue.create_comment("name, description and version are required in metadata.yml.") + return ValueError("name, description and version are required in metadata.yml.") + + # 不检测重复资源包,因为资源包可能有多个版本 + # 检测通过,编辑原issue + metadata_markdown = f"**名称**: {data.get('name')}\n**描述**: {data.get('description')}\n**版本**: {data.get('version')}\n" + for k, v in data.items(): + if k not in ("name", "description", "version"): + metadata_markdown += f"**{k}**: {v}\n" + + new_issue_body = f"---\nname: {name}\ndesc: {desc}\nurl: {url}\nhomepage: {homepage}\nauthor: {author}\n---\n" + new_issue_body += f"# Resource: {name}\n" + new_issue_body += f"## 发布信息\n{desc}\n" + new_issue_body += f"**名称**: {name}\n" + new_issue_body += f"**描述**: {desc}\n" + new_issue_body += f"**作者**: {author}\n" + new_issue_body += f"**主页**: {homepage}\n" + new_issue_body += f"**下载**: {url}\n" + # 遍历其他字段 + for k, v in data.items(): + if k not in ("name", "description", "version"): + new_issue_body += f"**{k}**: {v}\n" + + issue.edit(new_issue_body) + issue.create_comment("✅ 预检查通过\n## 元数据\n" + metadata_markdown) + return nil + + +# closed +def add_resource(github: Github, issue: Issue, repo: Repository): + parser = MarkdownParser(issue.body) + parser.parse_front_matters() + name = parser.front_matters.get("name") + desc = parser.front_matters.get("desc") + url = parser.front_matters.get("url") + homepage = parser.front_matters.get("homepage") # optional + author = parser.front_matters.get("author") + + # 编辑仓库内的json文件 + resources = json.load(open(RESOURCE_JSON)) + resources.append({ + "name" : name, + "desc" : desc, + "url" : url, + "homepage": homepage, + "author" : author + }) + ref = repo.get_git_ref("heads/main") + tree = repo.create_git_tree( + base_tree=repo.get_git_commit(ref.object.sha).tree, + tree=[ + InputGitTreeElement( + path=RESOURCE_JSON, + mode="100644", + type="blob", + content=json.dumps(resources, indent=4, ensure_ascii=False) + ) + ] + ) + commit = repo.create_git_commit( + message=f":package: 发布资源: {name}", + tree=tree, + parents=[repo.get_git_commit(ref.object.sha)] + ) + ref.edit(commit.sha) + + +def handle_resource(github: Github, issue: Issue, repo: Repository, act_type: str): + if act_type in (OPENED, EDITED): + pre_check(github, issue, repo) + + elif act_type == CLOSED: + add_resource(github, issue, repo) + else: + print("No operation found for the issue: ", act_type) diff --git a/liteyuki_flow/ts.md b/liteyuki_flow/ts.md new file mode 100644 index 00000000..ec37599c --- /dev/null +++ b/liteyuki_flow/ts.md @@ -0,0 +1,26 @@ +--- +# 请修改此处内容/Please modify the content here +# 不要修改正文中的内容/Do not modify the content in the body +name: PluginName +desc: 插件描述 +author: 作者名 +link: 下载链接 +homepage: 主页链接 +--- + +# Plugin: PluginName + +## 插件信息 + +- **包名**: PluginName +- **描述**: 插件描述 +- **作者**: AuthorName +- **主页**: https://a.b.c + +## 资源包信息 + +- **名称**: PluginName +- **描述**: 插件描述 +- **作者**: AuthorName +- **主页**: https://a.b.c +- **下载**: https://a.b.c \ No newline at end of file diff --git a/liteyuki_flow/typ.py b/liteyuki_flow/typ.py new file mode 100644 index 00000000..421207af --- /dev/null +++ b/liteyuki_flow/typ.py @@ -0,0 +1,20 @@ +""" +Module docs +""" +from typing import TypeAlias + + +class Nil(): + def __eq__(self, other): + if isinstance(other, Nil): + return True + return other is None + + # 不等于 + def __ne__(self, other): + return not self.__eq__(other) + + +nil = Nil() + +err: TypeAlias = Exception | Nil \ No newline at end of file