diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index ac727428..20a4b0e6 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -20,7 +20,6 @@ jobs: fetch-depth: 0 # 如果你文档需要 Git 子模块,取消注释下一行 # submodules: true - - name: 安装 pnpm uses: pnpm/action-setup@v2 with: @@ -41,7 +40,7 @@ jobs: - name: 生成API markdown run: |- - python -m pip install pydantic + python -m pip install litedoc python -m litedoc liteyuki -o docs/dev/api -l zh-Hans -t vuepress python -m litedoc liteyuki -o docs/en/dev/api -l en -t vuepress diff --git a/litedoc/__init__.py b/litedoc/__init__.py deleted file mode 100644 index 31a97612..00000000 --- a/litedoc/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved - -@Time : 2024/8/28 下午12:52 -@Author : snowykami -@Email : snowykami@outlook.com -@File : __init__.py.py -@Software: PyCharm -""" \ No newline at end of file diff --git a/litedoc/__main__.py b/litedoc/__main__.py deleted file mode 100644 index db92b7b9..00000000 --- a/litedoc/__main__.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved - -@Time : 2024/8/28 下午4:08 -@Author : snowykami -@Email : snowykami@outlook.com -@File : __main__.py -@Software: PyCharm -""" -# command line tool -# args[0] path -# -o|--output output path -# -l|--lang zh-Hans en jp default zh-Hans - - -import argparse -import os -import sys - -from litedoc.output import generate_from_module - - -def main(): - parser = argparse.ArgumentParser(description="Generate documentation from Python modules.") - parser.add_argument("path", type=str, help="Path to the Python module or package.") - parser.add_argument("-o", "--output", default="doc-output", type=str, help="Output directory.") - parser.add_argument("-c", "--contain-top", action="store_true", help="Whether to contain top-level dir in output dir.") - parser.add_argument("-l", "--lang", default="zh_Hans", type=str, help="Languages of the document.") - parser.add_argument("-t", "--theme", default="vitepress", type=str, help="Theme of the document.") - parser.add_argument("-s", "--style", default="google", type=str, help="Style of the document.") - - args = parser.parse_args() - - if not os.path.exists(args.path): - print(f"Error: The path {args.path} does not exist.") - sys.exit(1) - - if not os.path.exists(args.output): - os.makedirs(args.output) - - lang = args.lang - - generate_from_module(args.path, args.output, with_top=args.contain_top, lang=lang, theme=args.theme, style=args.style) - - -if __name__ == '__main__': - main() diff --git a/litedoc/docstring/__init__.py b/litedoc/docstring/__init__.py deleted file mode 100644 index 323b116a..00000000 --- a/litedoc/docstring/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- 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 -""" \ No newline at end of file diff --git a/litedoc/docstring/docstring.py b/litedoc/docstring/docstring.py deleted file mode 100644 index 4b3344cf..00000000 --- a/litedoc/docstring/docstring.py +++ /dev/null @@ -1,157 +0,0 @@ -# -*- 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, Field - -from litedoc.i18n import get_text - - -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): - desc: str = "" - args: list[Args] = [] - attrs: list[Attr] = [] - return_: Optional[Return] = None - raise_: list[Exception_] = [] - example: list[Example] = [] - - 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 = "", input_: str = "", output: str = ""): - self.example.append(Example(desc=desc, input=input_, output=output)) - - def reduction(self) -> str: - """ - 通过解析结果还原docstring - Args: - - Returns: - - """ - ret = "" - 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" - for example in self.example: - ret += f" {example.desc}\n Input: {example.input}\n Output: {example.output}\n" - - return ret - - def markdown(self, lang: str, indent: int = 4, is_classmethod: bool = False) -> str: - """ - 生成markdown文档 - Args: - is_classmethod: - lang: - indent: - - Returns: - - """ - PREFIX = "" * indent - ret = "" - # ret += self.desc + "\n\n" - # print(self.reduction()) - # print(self.desc, self.return_) - # 单数属性 - if self.desc: - ret += PREFIX + f"\n**{get_text(lang, 'desc')}**: {self.desc}\n" - - # 复数属性 - if self.args: - ret += PREFIX + f"\n**{get_text(lang, 'docstring.args')}**:\n" - for arg in self.args: - ret += PREFIX + f"> - {arg.name}: {arg.type} {arg.desc}\n" - if self.attrs: - ret += PREFIX + f"\n**{get_text(lang, 'docstring.attrs')}**:\n" - for attr in self.attrs: - ret += PREFIX + f"> - {attr.name}: {attr.type} {attr.desc}\n" - - # 单数属性 - if self.return_ is not None: - ret += PREFIX + f"\n**{get_text(lang, 'docstring.return')}**: {self.return_.desc}\n" - # 复数属性 - if self.raise_: - ret += PREFIX + f"\n**{get_text(lang, 'docstring.raises')}**:\n" - for exception in self.raise_: - ret += PREFIX + f"> - {exception.name} {exception.desc}\n" - if self.example: - ret += PREFIX + f"\n**{get_text(lang, 'docstring.example')}**:\n" - for example in self.example: - ret += PREFIX + f" - {example.desc}\n> **{get_text(lang, 'docs.input')}**: {example.input}\n> **{get_text(lang, 'docs.output')}**: {example.output}\n" - return ret - - def __str__(self): - return self.desc diff --git a/litedoc/docstring/parser.py b/litedoc/docstring/parser.py deleted file mode 100644 index 24b7c20a..00000000 --- a/litedoc/docstring/parser.py +++ /dev/null @@ -1,192 +0,0 @@ -""" -Google docstring parser for Python. -""" -from typing import Optional - -from litedoc.docstring.docstring import Docstring - - -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): - self.lines = docstring.splitlines() - self.indent = indent - self.lineno = 0 # Current line number - self.char = 0 # Current character position - - self.docstring = Docstring() - - 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(name.strip(), desc.strip()) - else: - self.docstring.add_arg(line.strip()) - - def parse_return(self): - """ - 解析返回值行 - """ - if line := self.match_next_line(): - self.docstring.add_return(line.strip()) - - def parse_raises(self): - """ - 解析异常行 - """ - while line := self.match_next_line(): - if ":" in line: - name, desc = line.split(":", 1) - self.docstring.add_raise(name.strip(), desc.strip()) - else: - self.docstring.add_raise(line.strip()) - - def parse_example(self): - """ - 解析示例行 - """ - while line := self.match_next_line(): - if ":" in line: - name, desc = line.split(":", 1) - self.docstring.add_example(name.strip(), desc.strip()) - else: - self.docstring.add_example(line.strip()) - - def parse_attrs(self): - """ - 解析属性行 - """ - while line := self.match_next_line(): - if ":" in line: - name, desc = line.split(":", 1) - self.docstring.add_attrs(name.strip(), desc.strip()) - else: - self.docstring.add_attrs(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: - """ - 逐行解析,直到遇到EOS - - 最开始未解析到的内容全部加入desc - - Returns: - - """ - add_desc = True - while self.lineno < len(self.lines): - token = self.match_token() - if token is None and add_desc: - self.docstring.add_desc(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) -> Docstring: - if parser == "google": - return GoogleDocstringParser(docstring, indent).parse() - else: - raise ValueError(f"Unknown parser: {parser}") diff --git a/litedoc/i18n.py b/litedoc/i18n.py deleted file mode 100644 index c45aea50..00000000 --- a/litedoc/i18n.py +++ /dev/null @@ -1,131 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Internationalization module. -""" -from typing import Optional, TypeAlias - -NestedDict: TypeAlias = dict[str, 'str | NestedDict'] - -i18n_dict: dict[str, NestedDict] = { - "en" : { - "docstring": { - "args" : "Arguments", - "return" : "Return", - "attribute": "Attribute", - "raises" : "Raises", - "example" : "Examples", - "yields" : "Yields", - }, - "src": "Source code", - "desc": "Description", - "type": "Type", - }, - "zh-Hans": { - "docstring": { - "args" : "参数", - "return" : "返回", - "attribute": "属性", - "raises" : "引发", - "example" : "示例", - "yields" : "产出", - }, - "src": "源代码", - "desc": "说明", - "type": "类型", - }, - "zh-Hant": { - "docstring": { - "args" : "變數説明", - "return" : "返回", - "attribute": "屬性", - "raises" : "抛出", - "example" : "範例", - "yields" : "產出", - }, - "src": "源碼", - "desc": "説明", - "type": "類型", - }, - "ja" : { - "docstring": { - "args" : "引数", - "return" : "戻り値", - "attribute": "属性", - "raises" : "例外", - "example" : "例", - "yields" : "生成", - }, - "src": "ソースコード", - "desc": "説明", - "type": "タイプ", - }, -} - - -def flat_i18n_dict(data: dict[str, NestedDict]) -> dict[str, dict[str, str]]: - """ - Flatten i18n_dict. - Examples: - ```python - { - "en": { - "docs": { - "key1": "val1", - "key2": "val2", - } - } - } - ``` - - to - - ```python - { - "en": { - "docs.key1": "val1", - "docs.key2": "val2", - } - } - ``` - Returns: - """ - ret: dict[str, dict[str, str]] = {} - - def _flat(_lang_data: NestedDict) -> dict[str, str]: - res = {} - for k, v in _lang_data.items(): - if isinstance(v, dict): - for kk, vv in _flat(v).items(): - res[f"{k}.{kk}"] = vv - else: - res[k] = v - return res - - for lang, lang_data in data.items(): - ret[lang] = _flat(lang_data) - - return ret - - -i18n_flat_dict = flat_i18n_dict(i18n_dict) - - -def get_text(lang: str, key: str, default: Optional[str] = None, fallback: Optional[str] = "en") -> str: - """ - Get text from i18n_dict. - Args: - lang: language name - key: text key - default: default text, if None return fallback language or key - fallback: fallback language, priority is higher than default - Returns: - str: text - """ - if lang in i18n_flat_dict: - if key in i18n_flat_dict[lang]: - return i18n_flat_dict[lang][key] - - if fallback is not None: - return i18n_flat_dict.get(fallback, {}).get(key, default or key) - else: - return default or key diff --git a/litedoc/output.py b/litedoc/output.py deleted file mode 100644 index 24de0fe2..00000000 --- a/litedoc/output.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved - -@Time : 2024/8/28 下午3:59 -@Author : snowykami -@Email : snowykami@outlook.com -@File : output.py -@Software: PyCharm -""" -import os.path - -from litedoc.style.markdown import generate -from litedoc.syntax.astparser import AstParser - - -def write_to_file(content: str, output: str) -> None: - """ - Write content to file. - - Args: - content: str, content to write. - output: str, path to output file. - """ - if not os.path.exists(os.path.dirname(output)): - os.makedirs(os.path.dirname(output)) - - with open(output, "w", encoding="utf-8") as f: - f.write(content) - - -def get_file_list(module_folder: str): - file_list = [] - for root, dirs, files in os.walk(module_folder): - for file in files: - if file.endswith((".py", ".pyi")): - file_list.append(os.path.join(root, file)) - return file_list - - -def get_relative_path(base_path: str, target_path: str) -> str: - """ - 获取相对路径 - Args: - base_path: 基础路径 - target_path: 目标路径 - """ - return os.path.relpath(target_path, base_path) - - -def generate_from_module(module_folder: str, - output_dir: str, - with_top: bool = False, - lang: str = "zh-Hans", - ignored_paths=None, - theme: str = "vitepress", - style: str = "google" - ): - """ - 生成文档 - Args: - module_folder: 模块文件夹 - output_dir: 输出文件夹 - with_top: 是否包含顶层文件夹 False时例如docs/api/module_a, docs/api/module_b, True时例如docs/api/module/module_a.md, docs/api/module/module_b.md - ignored_paths: 忽略的路径 - lang: 语言 - theme: 主题 - style: 样式 - """ - if ignored_paths is None: - ignored_paths = [] - file_data: dict[str, str] = {} # 路径 -> 字串 - - file_list = get_file_list(module_folder) - - # 清理输出目录 - if not os.path.exists(output_dir): - os.makedirs(output_dir) - - replace_data = { - "__init__": "index" if theme == "vitepress" else "README", - ".py" : ".md", - } - - for pyfile_path in file_list: - if any(ignored_path.replace("\\", "/") in pyfile_path.replace("\\", "/") for ignored_path in ignored_paths): - continue - - no_module_name_pyfile_path = get_relative_path(module_folder, pyfile_path) # 去头路径 - - # markdown相对路径 - rel_md_path = pyfile_path if with_top else no_module_name_pyfile_path - for rk, rv in replace_data.items(): - rel_md_path = rel_md_path.replace(rk, rv) - - abs_md_path = os.path.join(output_dir, rel_md_path) - - # 获取模块信息 - ast_parser = AstParser(open(pyfile_path, "r", encoding="utf-8").read()) - - # 生成markdown - - front_matter = { - "title": pyfile_path.replace("\\", "/"). - replace("/", "."). - replace(".py", ""). - replace(".__init__", ""), - - } - - md_content = generate(ast_parser, lang=lang, frontmatter=front_matter) - print(f"Generate {pyfile_path} -> {abs_md_path}") - file_data[abs_md_path] = md_content - - for fn, content in file_data.items(): - write_to_file(content, fn) diff --git a/litedoc/style/__init__.py b/litedoc/style/__init__.py deleted file mode 100644 index fb9c65ff..00000000 --- a/litedoc/style/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved - -@Time : 2024/8/28 下午3:39 -@Author : snowykami -@Email : snowykami@outlook.com -@File : __init__.py.py -@Software: PyCharm -""" \ No newline at end of file diff --git a/litedoc/style/markdown.py b/litedoc/style/markdown.py deleted file mode 100644 index 32b97445..00000000 --- a/litedoc/style/markdown.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved - -@Time : 2024/8/28 下午3:39 -@Author : snowykami -@Email : snowykami@outlook.com -@File : markdown.py -@Software: PyCharm -""" -from typing import Optional - -from litedoc.syntax.astparser import AstParser -from litedoc.syntax.node import * -from litedoc.i18n import get_text - - -def generate(parser: AstParser, lang: str, frontmatter: Optional[dict] = None, style: str = "google") -> str: - """ - Generate markdown style document from ast - You can modify this function to generate markdown style that enjoys you - Args: - parser: - lang: language - frontmatter: - style: style of docs - Returns: - markdown style document - """ - if frontmatter is not None: - md = "---\n" - for k, v in frontmatter.items(): - md += f"{k}: {v}\n" - md += "---\n" - else: - md = "" - - # var > func > class - - """遍历函数""" - for func in parser.functions: - if func.name.startswith("_"): - continue - md += func.markdown(lang) - - """遍历类""" - - for cls in parser.classes: - md += cls.markdown(lang) - - """遍历变量""" - for var in parser.variables: - md += f"### ***var*** `{var.name} = {var.value}`\n\n" - if var.type != TypeHint.NO_TYPEHINT: - md += f"- **{get_text(lang, 'type')}**: `{var.type}`\n\n" - - if var.docs is not None: - md += f"- **{get_text(lang, 'desc')}**: {var.docs}\n\n" - - return md diff --git a/litedoc/syntax/astparser.py b/litedoc/syntax/astparser.py deleted file mode 100644 index fb416fe8..00000000 --- a/litedoc/syntax/astparser.py +++ /dev/null @@ -1,300 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved - -@Time : 2024/8/28 下午2:13 -@Author : snowykami -@Email : snowykami@outlook.com -@File : astparser.py -@Software: PyCharm -""" -import ast -import inspect - -from .node import * -from ..docstring.parser import parse - -class AstParser: - def __init__(self, code: str, style: str = "google"): - """ - 从代码解析AST - Args: - code: 代码 - style: 注释风格 - """ - self.style = style - self.code = code - self.tree = ast.parse(code) - - self.classes: list[ClassNode] = [] - self.functions: list[FunctionNode] = [] - self.variables: list[AssignNode] = [] - - self.parse() - - @staticmethod - def clear_quotes(s: str) -> str: - """ - 去除类型注解中的引号 - Args: - s: - Returns: - """ - return s.replace("'", "").replace('"', "") - - def get_line_content(self, lineno: int, ignore_index_out: bool = True) -> str: - """获取代码行内容 - Args: - lineno: 行号 - ignore_index_out: 是否忽略索引越界 - Returns: - 代码行内容 - """ - if ignore_index_out: - if lineno < 1 or lineno > len(self.code.split("\n")): - return "" - return self.code.split("\n")[lineno - 1] - - @staticmethod - def match_line_docs(linecontent: str) -> str: - """匹配行内注释 - Args: - linecontent: 行内容 - Returns: - 文档字符串 - """ - in_string = False - string_char = '' - for i, char in enumerate(linecontent): - if char in ('"', "'"): - if in_string: - if char == string_char: - in_string = False - else: - in_string = True - string_char = char - elif char == '#' and not in_string: - return linecontent[i + 1:].strip() - return "" - - def parse(self): - for node in ast.walk(self.tree): - if isinstance(node, ast.ClassDef): - if not self._is_module_level_class(node): - continue - - class_node = ClassNode( - name=node.name, - docs=parse(ast.get_docstring(node), parser=self.style) if ast.get_docstring(node) else None, - inherits=[ast.unparse(base) for base in node.bases] - ) - self.classes.append(class_node) - - # 继续遍历类内部的函数 - for sub_node in node.body: - if isinstance(sub_node, (ast.FunctionDef, ast.AsyncFunctionDef)): - class_node.methods.append(FunctionNode( - name=sub_node.name, - docs=parse(ast.get_docstring(sub_node), parser=self.style) if ast.get_docstring(sub_node) else None, - posonlyargs=[ - ArgNode( - name=arg.arg, - type=self.clear_quotes(ast.unparse(arg.annotation).strip()) if arg.annotation else TypeHint.NO_TYPEHINT, - ) - for arg in sub_node.args.posonlyargs - ], - args=[ - ArgNode( - name=arg.arg, - type=self.clear_quotes(ast.unparse(arg.annotation).strip()) if arg.annotation else TypeHint.NO_TYPEHINT, - ) - for arg in sub_node.args.args - ], - kwonlyargs=[ - ArgNode( - name=arg.arg, - type=self.clear_quotes(ast.unparse(arg.annotation).strip()) if arg.annotation else TypeHint.NO_TYPEHINT, - ) - for arg in sub_node.args.kwonlyargs - ], - kw_defaults=[ - ConstantNode( - value=ast.unparse(default).strip() if default else TypeHint.NO_DEFAULT - ) - for default in sub_node.args.kw_defaults - ], - defaults=[ - ConstantNode( - value=ast.unparse(default).strip() if default else TypeHint.NO_DEFAULT - ) - for default in sub_node.args.defaults - ], - return_=self.clear_quotes(ast.unparse(sub_node.returns).strip()) if sub_node.returns else TypeHint.NO_RETURN, - decorators=[ast.unparse(decorator).strip() for decorator in sub_node.decorator_list], - is_async=isinstance(sub_node, ast.AsyncFunctionDef), - src=ast.unparse(sub_node).strip(), - is_classmethod=True - )) - # elif isinstance(sub_node, (ast.Assign, ast.AnnAssign)): - # if isinstance(sub_node, ast.Assign): - # class_node.attrs.append(AttrNode( - # name=sub_node.targets[0].id, # type: ignore - # type=TypeHint.NO_TYPEHINT, - # value=ast.unparse(sub_node.value).strip() - # )) - # elif isinstance(sub_node, ast.AnnAssign): - # class_node.attrs.append(AttrNode( - # name=sub_node.target.id, - # type=ast.unparse(sub_node.annotation).strip(), - # value=ast.unparse(sub_node.value).strip() if sub_node.value else TypeHint.NO_DEFAULT - # )) - # else: - # raise ValueError(f"Unsupported node type: {type(sub_node)}") - - elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): - # 仅打印模块级别的函数 - if not self._is_module_level_function(node): - continue - - self.functions.append(FunctionNode( - name=node.name, - docs=parse(ast.get_docstring(node), parser=self.style) if ast.get_docstring(node) else None, - posonlyargs=[ - ArgNode( - name=arg.arg, - type=self.clear_quotes(ast.unparse(arg.annotation).strip()) if arg.annotation else TypeHint.NO_TYPEHINT, - ) - for arg in node.args.posonlyargs - ], - args=[ - ArgNode( - name=arg.arg, - type=self.clear_quotes(ast.unparse(arg.annotation).strip()) if arg.annotation else TypeHint.NO_TYPEHINT, - ) - for arg, default in zip(node.args.args, node.args.defaults) - ], - kwonlyargs=[ - ArgNode( - name=arg.arg, - type=self.clear_quotes(ast.unparse(arg.annotation).strip()) if arg.annotation else TypeHint.NO_TYPEHINT, - ) - for arg in node.args.kwonlyargs - ], - kw_defaults=[ - ConstantNode( - value=ast.unparse(default).strip() if default else TypeHint.NO_DEFAULT - ) - for default in node.args.kw_defaults - ], - defaults=[ - ConstantNode( - value=ast.unparse(default).strip() if default else TypeHint.NO_DEFAULT - ) - for default in node.args.defaults - ], - return_=self.clear_quotes(ast.unparse(node.returns).strip()) if node.returns else TypeHint.NO_RETURN, - decorators=[ast.unparse(decorator).strip() for decorator in node.decorator_list], - is_async=isinstance(node, ast.AsyncFunctionDef), - src=ast.unparse(node).strip() - )) - - elif isinstance(node, (ast.Assign, ast.AnnAssign)): - if not self._is_module_level_variable2(node): - continue - else: - pass - lineno = node.lineno - prev_line = self.get_line_content(lineno - 1).strip() - curr_line = self.get_line_content(lineno).strip() - next_line = self.get_line_content(lineno + 1).strip() - - # 获取文档字符串,优先检测下行""" - if next_line.startswith('"""'): - docs = next_line[3:-3] - elif prev_line.startswith('"""'): - docs = prev_line[3:-3] - else: - curr_docs = self.match_line_docs(curr_line) - if curr_docs: - docs = curr_docs - else: - docs = None - - # if isinstance(node, ast.Assign): - # for target in node.targets: - # if isinstance(target, ast.Name): - # self.variables.append(AssignNode( - # name=target.id, - # value=ast.unparse(node.value).strip(), - # type=ast.unparse(node.annotation).strip() if isinstance(node, ast.AnnAssign) else TypeHint.NO_TYPEHINT - # )) - if isinstance(node, ast.AnnAssign): - self.variables.append(AssignNode( - name=node.target.id, - value=ast.unparse(node.value).strip() if node.value else TypeHint.NO_DEFAULT, - type=ast.unparse(node.annotation).strip(), - docs=docs - )) - - def _is_module_level_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef): - for parent in ast.walk(self.tree): - if isinstance(parent, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): - if node in parent.body: - return False - return True - - def _is_module_level_class(self, node: ast.ClassDef): - for parent in ast.walk(self.tree): - if isinstance(parent, ast.ClassDef): - if node in parent.body: - return False - return True - - def _is_module_level_variable(self, node: ast.Assign | ast.AnnAssign): - """在类方法或函数内部的变量不会被记录""" - - # for parent in ast.walk(self.tree): - # if isinstance(parent, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): - # if node in parent.body: - # return False - # else: - # for sub_node in parent.body: - # if isinstance(sub_node, (ast.FunctionDef, ast.AsyncFunctionDef)): - # if node in sub_node.body: - # return False - # return True - # 递归检查 - def _check(_node, _parent): - if isinstance(_parent, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): - if _node in _parent.body: - return False - else: - for sub_node in _parent.body: - if isinstance(sub_node, (ast.FunctionDef, ast.AsyncFunctionDef)): - return _check(_node, sub_node) - return True - - for parent in ast.walk(self.tree): - if not _check(node, parent): - return False - return True - - def _is_module_level_variable2(self, node: ast.Assign | ast.AnnAssign) -> bool: - """ - 检查变量是否在模块级别定义。 - """ - for parent in ast.walk(self.tree): - if isinstance(parent, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): - if node in parent.body: - return False - return True - - def __str__(self): - s = "" - for cls in self.classes: - s += f"class {cls.name}:\n" - for func in self.functions: - s += f"def {func.name}:\n" - for var in self.variables: - s += f"{var.name} = {var.value}\n" - return s diff --git a/litedoc/syntax/node.py b/litedoc/syntax/node.py deleted file mode 100644 index 4891b10d..00000000 --- a/litedoc/syntax/node.py +++ /dev/null @@ -1,314 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved - -@Time : 2024/8/28 下午2:14 -@Author : snowykami -@Email : snowykami@outlook.com -@File : node.py -@Software: PyCharm -""" -from typing import Literal, Optional -from enum import Enum - -from pydantic import BaseModel, Field - -from litedoc.docstring.docstring import Docstring -from litedoc.i18n import get_text - - -class TypeHint: - NO_TYPEHINT = "NO_TYPE_HINT" - NO_DEFAULT = "NO_DEFAULT" - NO_RETURN = "NO_RETURN" - - -class AssignNode(BaseModel): - """ - AssignNode is a pydantic model that represents an assignment. - Attributes: - name: str - The name of the assignment. - type: str = "" - The type of the assignment. - value: str - The value of the assignment. - """ - name: str - type: str = "" - value: str - docs: Optional[str] = "" - - -class ArgNode(BaseModel): - """ - ArgNode is a pydantic model that represents an argument. - Attributes: - name: str - The name of the argument. - type: str = "" - The type of the argument. - default: str = "" - The default value of the argument. - """ - name: str - type: str = TypeHint.NO_TYPEHINT - - -class AttrNode(BaseModel): - """ - AttrNode is a pydantic model that represents an attribute. - Attributes: - name: str - The name of the attribute. - type: str = "" - The type of the attribute. - value: str = "" - The value of the attribute - """ - name: str - type: str = "" - value: str = "" - - -class ImportNode(BaseModel): - """ - ImportNode is a pydantic model that represents an import statement. - Attributes: - name: str - The name of the import statement. - as_: str = "" - The alias of the import - """ - name: str - as_: str = "" - - -class ConstantNode(BaseModel): - """ - ConstantNode is a pydantic model that represents a constant. - Attributes: - value: str - The value of the constant. - """ - value: str - - -class FunctionNode(BaseModel): - """ - FunctionNode is a pydantic model that represents a function. - Attributes: - name: str - The name of the function. - docs: str = "" - The docstring of the function. - args: list[ArgNode] = [] - The arguments of the function. - return_: ReturnNode = None - The return value of the function. - decorators: list[str] = [] - The decorators of the function. - is_async: bool = False - Whether the function is asynchronous. - """ - name: str - docs: Optional[Docstring] = None - - posonlyargs: list[ArgNode] = [] - args: list[ArgNode] = [] - kwonlyargs: list[ArgNode] = [] - kw_defaults: list[ConstantNode] = [] - defaults: list[ConstantNode] = [] - - return_: str = TypeHint.NO_RETURN - decorators: list[str] = [] - src: str - is_async: bool = False - is_classmethod: bool = False - - magic_methods: dict[str, str] = { - "__add__" : "+", - "__radd__" : "+", - "__sub__" : "-", - "__rsub__" : "-", - "__mul__" : "*", - "__rmul__" : "*", - "__matmul__" : "@", - "__rmatmul__": "@", - "__mod__" : "%", - "__truediv__": "/", - "__rtruediv__": "/", - "__neg__" : "-", - } # 魔术方法, 例如运算符 - - def is_private(self): - """ - Check if the function or method is private. - Returns: - bool: True if the function or method is private, False otherwise. - """ - return self.name.startswith("_") - - def is_builtin(self): - """ - Check if the function or method is a builtin function or method. - Returns: - bool: True if the function or method is a builtin function or method, False otherwise. - """ - return self.name.startswith("__") and self.name.endswith("__") - - def markdown(self, lang: str, indent: int = 0) -> str: - """ - Args: - indent: int - The number of spaces to indent the markdown. - lang: str - The language of the - Returns: - markdown style document - """ - self.complete_default_args() - PREFIX = "" * indent - # if is_classmethod: - # PREFIX = "- #" - func_type = "func" if not self.is_classmethod else "method" - - md = "" - # 装饰器部分 - if len(self.decorators) > 0: - for decorator in self.decorators: - md += PREFIX + f"### `@{decorator}`\n" - - if self.is_async: - md += PREFIX + f"### *async {func_type}* " - else: - md += PREFIX + f"### *{func_type}* " - - # code start - # 配对位置参数和位置参数默认值,无默认值用TypeHint.NO_DEFAULT - args: list[str] = [] # 可直接", ".join(args)得到位置参数部分 - arg_i = 0 - - if len(self.posonlyargs) > 0: - for arg in self.posonlyargs: - arg_text = f"{arg.name}" - if arg.type != TypeHint.NO_TYPEHINT: - arg_text += f": {arg.type}" - arg_default = self.defaults[arg_i].value - if arg_default != TypeHint.NO_DEFAULT: - arg_text += f" = {arg_default}" - args.append(arg_text) - arg_i += 1 - # 加位置参数分割符 / - args.append("/") - - for arg in self.args: - arg_text = f"{arg.name}" - if arg.type != TypeHint.NO_TYPEHINT: - arg_text += f": {arg.type}" - arg_default = self.defaults[arg_i].value - if arg_default != TypeHint.NO_DEFAULT: - arg_text += f" = {arg_default}" - args.append(arg_text) - arg_i += 1 - - if len(self.kwonlyargs) > 0: - # 加关键字参数分割符 * - args.append("*") - for arg, kw_default in zip(self.kwonlyargs, self.kw_defaults): - arg_text = f"{arg.name}" - if arg.type != TypeHint.NO_TYPEHINT: - arg_text += f": {arg.type}" - if kw_default.value != TypeHint.NO_DEFAULT: - arg_text += f" = {kw_default.value}" - args.append(arg_text) - - """魔法方法""" - if self.name in self.magic_methods: - if len(args) == 2: - md += f"`{args[0]} {self.magic_methods[self.name]} {args[1]}" - elif len(args) == 1: - md += f"`{self.magic_methods[self.name]} {args[0]}" - if self.return_ != TypeHint.NO_RETURN: - md += f" => {self.return_}" - else: - md += f"`{self.name}(" # code start - md += ", ".join(args) + ")" - if self.return_ != TypeHint.NO_RETURN: - md += f" -> {self.return_}" - - md += "`\n\n" # code end - - """此处预留docstring""" - if self.docs is not None: - md += f"\n{self.docs.markdown(lang, indent)}\n" - else: - pass - # 源码展示 - md += PREFIX + f"\n
\n {get_text(lang, 'src')} \n\n```python\n{self.src}\n```\n
\n\n" - - return md - - def complete_default_args(self): - """ - 补全位置参数默认值,用无默认值插入 - Returns: - - """ - num = len(self.args) + len(self.posonlyargs) - len(self.defaults) - self.defaults = [ConstantNode(value=TypeHint.NO_DEFAULT) for _ in range(num)] + self.defaults - - def __str__(self): - return f"def {self.name}({', '.join([f'{arg.name}: {arg.type} = {arg.default}' for arg in self.args])}) -> {self.return_}" - - -class ClassNode(BaseModel): - """ - ClassNode is a pydantic model that represents a class. - Attributes: - name: str - The name of the class. - docs: str = "" - The docstring of the class. - attrs: list[AttrNode] = [] - The attributes of the class. - methods: list[MethodNode] = [] - The methods of the class. - inherits: list["ClassNode"] = [] - The classes that the class inherits from - """ - name: str - docs: Optional[Docstring] = None - attrs: list[AttrNode] = [] - methods: list[FunctionNode] = [] - inherits: list[str] = [] - - def markdown(self, lang: str) -> str: - """ - 返回类的markdown文档 - Args: - lang: str - The language of the - Returns: - markdown style document - """ - hidden_methods = [ - "__str__", - "__repr__", - ] - md = "" - md += f"### **class** `{self.name}" - if len(self.inherits) > 0: - md += f"({', '.join([cls for cls in self.inherits])})" - md += "`\n" - for method in self.methods: - if method.name in hidden_methods: - continue - md += method.markdown(lang, 2) - for attr in self.attrs: - if attr.type == TypeHint.NO_TYPEHINT: - md += f"#### ***attr*** `{attr.name} = {attr.value}`\n\n" - else: - md += f"#### ***attr*** `{attr.name}: {attr.type} = {attr.value}`\n\n" - - return md diff --git a/litedoc/translator.py b/litedoc/translator.py deleted file mode 100644 index d9f93e09..00000000 --- a/litedoc/translator.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved - -@Time : 2024/8/29 下午12:02 -@Author : snowykami -@Email : snowykami@outlook.com -@File : translator.py -@Software: PyCharm -""" -from typing import Optional - -from translate import Translator # type: ignore - -# 特殊映射语言 -i18n_lang2googletrans_lang = { - "zh-Hans": "zh-cn", - "zh-Hant": "zh-tw", - "en" : "en", -} - - -def get_google_lang(lang: str) -> str: - """ - Get google translate language - Args: - lang: language - Returns: - google translate language - """ - return i18n_lang2googletrans_lang.get(lang, lang) - - -def translate(text: str, lang: str, source_lang: str) -> str: - """ - Translate text to target language - Args: - source_lang: - text: text - lang: target language - Returns: - translated text - """ - if lang == source_lang: - return text - google_lang = get_google_lang(lang) - return Translator(to_lang=google_lang, from_lang=source_lang).translate(text)