Files
RustPython/scripts/generate_docs.py
2022-04-22 14:42:42 -05:00

134 lines
3.8 KiB
Python

import sys
import warnings
from pydoc import ModuleScanner
def scan_modules():
"""taken from the source code of help('modules')
https://github.com/python/cpython/blob/63298930fb531ba2bb4f23bc3b915dbf1e17e9e1/Lib/pydoc.py#L2178"""
modules = {}
def callback(path, modname, desc, modules=modules):
if modname and modname[-9:] == ".__init__":
modname = modname[:-9] + " (package)"
if modname.find(".") < 0:
modules[modname] = 1
def onerror(modname):
callback(None, modname, None)
with warnings.catch_warnings():
# ignore warnings from importing deprecated modules
warnings.simplefilter("ignore")
ModuleScanner().run(callback, onerror=onerror)
return list(modules.keys())
def import_module(module_name):
import io
from contextlib import redirect_stdout
# Importing modules causes ('Constant String', 2, None, 4) and
# "Hello world!" to be printed to stdout.
f = io.StringIO()
with warnings.catch_warnings(), redirect_stdout(f):
# ignore warnings caused by importing deprecated modules
warnings.filterwarnings("ignore", category=DeprecationWarning)
try:
module = __import__(module_name)
except Exception as e:
return e
return module
def is_child(module, item):
import inspect
item_mod = inspect.getmodule(item)
return item_mod is module
def traverse(module, names, item):
import inspect
has_doc = inspect.ismodule(item) or inspect.isclass(item) or inspect.isbuiltin(item)
if has_doc and isinstance(item.__doc__, str):
yield names, item.__doc__
attr_names = dir(item)
for name in attr_names:
if name in [
"__class__",
"__dict__",
"__doc__",
"__objclass__",
"__name__",
"__qualname__",
"__annotations__",
]:
continue
try:
attr = getattr(item, name)
except AttributeError:
assert name == "__abstractmethods__", name
continue
if module is item and not is_child(module, attr):
continue
is_type_or_module = (type(attr) is type) or (type(attr) is type(__builtins__))
new_names = names.copy()
new_names.append(name)
if item == attr:
pass
elif not inspect.ismodule(item) and inspect.ismodule(attr):
pass
elif is_type_or_module:
yield from traverse(module, new_names, attr)
elif (
callable(attr)
or not issubclass(type(attr), type)
or type(attr).__name__ in ("getset_descriptor", "member_descriptor")
):
if inspect.isbuiltin(attr):
yield new_names, attr.__doc__
else:
assert False, (module, new_names, attr, type(attr).__name__)
def traverse_all():
for module_name in scan_modules():
if module_name in ("this", "antigravity"):
continue
module = import_module(module_name)
if hasattr(module, "__cached__"): # python module
continue
yield from traverse(module, [module_name], module)
def docs():
return ((".".join(names), doc) for names, doc in traverse_all())
if __name__ == "__main__":
import sys
import json
try:
out_path = sys.argv[1]
except IndexError:
out_path = "-"
def dump(docs):
yield "[\n"
for name, doc in docs:
if doc is None:
yield f" ({json.dumps(name)}, None),\n"
else:
yield f" ({json.dumps(name)}, Some({json.dumps(doc)})),\n"
yield "]\n"
out_file = open(out_path, "w") if out_path != "-" else sys.stdout
out_file.writelines(dump(docs()))