-
Notifications
You must be signed in to change notification settings - Fork 0
/
mdsource.py
122 lines (90 loc) · 3.7 KB
/
mdsource.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import ast
import os
from pathlib import Path
GITHUB_PREFIX = "https://github.com/occipital/django-content-settings/blob/master/"
SOURCE_FOLDER = "content_settings"
IGNORE_MODULES = ["receivers.py", "apps.py", "admin.py"]
def split_path(path):
return list(Path(path).parts)
def path_to_linux(path):
return "/".join(split_path(path))
def get_base_classes(bases):
"""Extract the names of base classes from the bases list in a class definition."""
base_class_names = []
for base in bases:
if isinstance(base, ast.Name):
base_class_names.append(base.id)
elif isinstance(base, ast.Attribute):
base_class_names.append(ast.unparse(base))
else:
base_class_names.append(ast.unparse(base))
return ", ".join(base_class_names)
def get_function_signature(func):
"""Generate the signature for a function or method."""
args = []
# Extract arguments and their default values
defaults = [None] * (
len(func.args.args) - len(func.args.defaults)
) + func.args.defaults
for arg, default in zip(func.args.args, defaults):
if isinstance(arg.annotation, ast.expr):
# Get the annotation if present
annotation = ast.unparse(arg.annotation)
arg_desc = f"{arg.arg}: {annotation}"
else:
arg_desc = arg.arg
if default is not None:
default_value = ast.unparse(default)
arg_desc += f" = {default_value}"
args.append(arg_desc)
return f"({', '.join(args)})"
def md_from_node(node, prefix, file_path):
for n in node.body:
if isinstance(n, ast.ClassDef):
if class_doc := ast.get_docstring(n):
yield f"\n\n{prefix} class {n.name}({get_base_classes(n.bases)})"
yield f"<sup>[source]({GITHUB_PREFIX}{path_to_linux(file_path)}#L{n.lineno})</sup>\n\n"
yield class_doc
yield from md_from_node(n, prefix=prefix + "#", file_path=file_path)
elif isinstance(n, ast.FunctionDef):
if func_doc := ast.get_docstring(n):
yield f"\n\n{prefix} def {n.name}"
yield get_function_signature(n)
yield f"<sup>[source]({GITHUB_PREFIX}{path_to_linux(file_path)}#L{n.lineno})</sup>\n\n"
yield func_doc
def md_from_file(file_path):
with open(file_path, "r") as file:
node = ast.parse(file.read(), filename=file_path)
if module_doc := ast.get_docstring(node):
yield module_doc
yield from md_from_node(node, prefix="###", file_path=file_path)
module_list = []
main_lines = []
for dirname, dirs, files in os.walk(SOURCE_FOLDER):
if dirname.endswith("__pycache__"):
continue
for name in sorted(files):
if not name.endswith(".py"):
continue
if name in IGNORE_MODULES:
continue
mddoc = "".join(md_from_file(os.path.join(dirname, name)))
if not mddoc:
continue
# Save generated doc to the docfile
dir = dirname[len(SOURCE_FOLDER) + 1 :]
module_name = ".".join(Path(dir).parts + (os.path.splitext(name)[0],))
main_lines.append(f"\n\n## {module_name}")
module_list.append(f"- [{module_name}](#{module_name.replace('.', '')})")
main_lines.append("\n\n")
main_lines.append(mddoc)
with open(os.path.join("docs", "source.md"), "w") as fh:
fh.write("# Module List\n\n")
fh.write("\n".join(module_list))
fh.write("\n\n")
fh.write("\n".join(main_lines))
fh.write(
"""
[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct-single.svg)](https://stand-with-ukraine.pp.ua)
"""
)