mkapi: Generate documentation
This commit is contained in:
parent
90e5da4b29
commit
dddbf1d4bc
9 changed files with 293 additions and 105 deletions
29
.github/workflows/docs.yml
vendored
Normal file
29
.github/workflows/docs.yml
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
name: Build docs
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
paths:
|
||||
- docs/**
|
||||
- py/**
|
||||
- tests/cpydiff/**
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- name: Install Python packages
|
||||
run: pip install -r docs/requirements.txt
|
||||
- name: Submodules
|
||||
run: make -C ports/m68kmac submodules
|
||||
- name: Build unix port
|
||||
run: source tools/ci.sh && ci_unix_build_helper
|
||||
- name: Build docs
|
||||
run: make -C docs/ html
|
||||
|
|
@ -3,7 +3,8 @@
|
|||
|
||||
# You can set these variables from the command line.
|
||||
PYTHON = python3
|
||||
SPHINXOPTS = -W --keep-going -j auto
|
||||
#SPHINXOPTS = -W --keep-going -j auto
|
||||
SPHINXOPTS = --keep-going -j auto
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = build/$(MICROPY_PORT)
|
||||
|
|
@ -58,12 +59,18 @@ clean:
|
|||
rm -rf $(BUILDDIR)/*
|
||||
rm -f $(GENRSTDIR)/*
|
||||
|
||||
doc-pre: cpydiff apidoc
|
||||
|
||||
cpydiff:
|
||||
@echo "Generating MicroPython Differences."
|
||||
rm -f $(GENRSTDIR)/*
|
||||
cd $(CPYDIFFDIR) && $(PYTHON) $(CPYDIFF)
|
||||
|
||||
html: cpydiff
|
||||
apidoc:
|
||||
@echo "Generating mac docs."
|
||||
$(MAKE) -C ../ports/m68kmac doc
|
||||
|
||||
html: doc-pre
|
||||
$(SPHINXBUILD) $(FORCE) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
|
@ -117,20 +124,20 @@ epub:
|
|||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex: cpydiff
|
||||
latex: doc-pre
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf: cpydiff
|
||||
latexpdf: doc-pre
|
||||
$(SPHINXBUILD) $(FORCE) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
latexpdfja: cpydiff
|
||||
latexpdfja: doc-pre
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
||||
|
|
|
|||
92
docs/autoapi/templates/python/module.rst
Normal file
92
docs/autoapi/templates/python/module.rst
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
{% if not obj.display %}
|
||||
:orphan:
|
||||
|
||||
{% endif %}
|
||||
:mod:`{{ obj.name }}`
|
||||
======={{ "=" * obj.name|length }}
|
||||
|
||||
.. py:module:: {{ obj.name }}
|
||||
|
||||
{% if obj.docstring %}
|
||||
.. autoapi-nested-parse::
|
||||
|
||||
{{ obj.docstring|prepare_docstring|indent(3) }}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% block subpackages %}
|
||||
{% set visible_subpackages = obj.subpackages|selectattr("display")|list %}
|
||||
{% if visible_subpackages %}
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
{% for subpackage in visible_subpackages %}
|
||||
{{ subpackage.short_name }}/index.rst
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block submodules %}
|
||||
{% set visible_submodules = obj.submodules|selectattr("display")|list %}
|
||||
{% if visible_submodules %}
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:maxdepth: 1
|
||||
|
||||
{% for submodule in visible_submodules %}
|
||||
{{ submodule.short_name }}/index.rst
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
{% if obj.all is not none %}
|
||||
{% set visible_children = obj.children|selectattr("short_name", "in", obj.all)|list %}
|
||||
{% elif obj.type is equalto("package") %}
|
||||
{% set visible_children = obj.children|selectattr("display")|list %}
|
||||
{% else %}
|
||||
{% set visible_children = obj.children|selectattr("display")|rejectattr("imported")|list %}
|
||||
{% endif %}
|
||||
{% if visible_children %}
|
||||
|
||||
{% set visible_classes = visible_children|selectattr("type", "equalto", "class")|list %}
|
||||
{% set visible_functions = visible_children|selectattr("type", "equalto", "function")|list %}
|
||||
{% if "show-module-summary" in autoapi_options and (visible_classes or visible_functions) %}
|
||||
{% block classes %}
|
||||
{% if visible_classes %}
|
||||
Classes
|
||||
~~~~~~~
|
||||
|
||||
.. autoapisummary::
|
||||
|
||||
{% for klass in visible_classes %}
|
||||
{{ klass.id }}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block functions %}
|
||||
{% if visible_functions %}
|
||||
Functions
|
||||
~~~~~~~~~
|
||||
|
||||
.. autoapisummary::
|
||||
|
||||
{% for function in visible_functions %}
|
||||
{{ function.id }}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
{% for obj_item in visible_children %}
|
||||
{{ obj_item.rendered|indent(0) }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
23
docs/conf.py
23
docs/conf.py
|
|
@ -49,6 +49,7 @@ micropy_authors = "MicroPython authors and contributors"
|
|||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
"autoapi.extension",
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.doctest",
|
||||
"sphinx.ext.intersphinx",
|
||||
|
|
@ -105,7 +106,7 @@ include_patterns = [
|
|||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ["build", ".venv"]
|
||||
exclude_patterns = ["build", ".venv", "autoapi"]
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
|
|
@ -337,3 +338,23 @@ texinfo_documents = [
|
|||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {"python": ("https://docs.python.org/3.5", None)}
|
||||
|
||||
# autoapi
|
||||
autoapi_type = "python"
|
||||
# Uncomment this if debugging autoapi
|
||||
# autoapi_keep_files = True
|
||||
autoapi_dirs = ["../ports/m68kmac/build/."]
|
||||
autoapi_add_toctree_entry = False
|
||||
autoapi_options = [
|
||||
"members",
|
||||
"undoc-members",
|
||||
"private-members",
|
||||
"show-inheritance",
|
||||
"special-members",
|
||||
"show-module-summary",
|
||||
]
|
||||
autoapi_template_dir = "autoapi/templates"
|
||||
autoapi_python_class_content = "both"
|
||||
autoapi_python_use_implicit_namespaces = False
|
||||
autoapi_root = "library/mac"
|
||||
autoapi_file_patterns = ["*.pyi"]
|
||||
|
|
|
|||
|
|
@ -84,6 +84,19 @@ library.
|
|||
zlib.rst
|
||||
_thread.rst
|
||||
|
||||
.. _micropython_lib_m68kmac:
|
||||
|
||||
Macintosh-specific libraries
|
||||
----------------------------
|
||||
|
||||
This section is auto-generated.
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
:maxdepth: 1
|
||||
|
||||
mac/**
|
||||
|
||||
.. _micropython_lib_micropython:
|
||||
|
||||
MicroPython-specific libraries
|
||||
|
|
@ -108,90 +121,6 @@ the following libraries.
|
|||
uctypes.rst
|
||||
vfs.rst
|
||||
|
||||
The following libraries provide drivers for hardware components.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
wm8960.rst
|
||||
|
||||
|
||||
Port-specific libraries
|
||||
-----------------------
|
||||
|
||||
In some cases the following port/board-specific libraries have functions or
|
||||
classes similar to those in the :mod:`machine` library. Where this occurs, the
|
||||
entry in the port specific library exposes hardware functionality unique to
|
||||
that platform.
|
||||
|
||||
To write portable code use functions and classes from the :mod:`machine` module.
|
||||
To access platform-specific hardware use the appropriate library, e.g.
|
||||
:mod:`pyb` in the case of the Pyboard.
|
||||
|
||||
|
||||
Libraries specific to the pyboard
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The following libraries are specific to the pyboard.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
pyb.rst
|
||||
stm.rst
|
||||
lcd160cr.rst
|
||||
|
||||
|
||||
Libraries specific to the WiPy
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The following libraries and classes are specific to the WiPy.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
wipy.rst
|
||||
machine.ADCWiPy.rst
|
||||
machine.TimerWiPy.rst
|
||||
|
||||
|
||||
Libraries specific to the ESP8266 and ESP32
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The following libraries are specific to the ESP8266 and ESP32.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
esp.rst
|
||||
esp32.rst
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
espnow.rst
|
||||
|
||||
|
||||
Libraries specific to the RP2040
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The following libraries are specific to the RP2040, as used in the Raspberry Pi Pico.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
rp2.rst
|
||||
|
||||
Libraries specific to Zephyr
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The following libraries are specific to the Zephyr port.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
zephyr.rst
|
||||
|
||||
.. _micropython_lib_extending:
|
||||
|
||||
Extending built-in libraries from Python
|
||||
|
|
|
|||
|
|
@ -30,6 +30,6 @@ the Macintosh ``Rect`` type.
|
|||
|
||||
To create a new ``Rect`` on the MicroPython heap, call the rect constructor. You can initialize fields using kwargs: ``r = Rect(top=48, left=8, bottom=308, right=504)``.
|
||||
|
||||
To treat a memory region as a ``Rect``, use ``Rect`` as the descriptor argument of :fun:`uctypes.struct`: ``uctypes.struct(addr, Rect)``.
|
||||
To treat a memory region as a ``Rect``, use ``Rect`` as the descriptor argument of :meth:`uctypes.struct`: ``uctypes.struct(addr, Rect)``.
|
||||
|
||||
A copy of a struct can be made with ``dest = src[:]`` and a struct can be updated in place with ``dest[:] = src``. The normal assignment operator ``dest = src`` merely makes ``dest`` refer to the same storage as ``src``. (Note that ``dest[:] = src`` does not currently type-check, but it may be made to do so in the future)
|
||||
|
|
|
|||
|
|
@ -1,2 +1,5 @@
|
|||
click
|
||||
sphinx~=7.2.6
|
||||
sphinx-autoapi
|
||||
sphinx-rtd-theme
|
||||
sphinxcontrib.jquery==4.1
|
||||
|
|
|
|||
|
|
@ -5,9 +5,14 @@ $$(BUILD)/mod$(1).c: $(TOP)/tools/mkapi.py $(filter-out -t,$(2)) $$(DEFS)
|
|||
$$(ECHO) "MKAPI $(1)"
|
||||
$$(Q)$$(MKDIR) -p $$(BUILD)
|
||||
$$(Q)$$(PYTHON) $(TOP)/tools/mkapi.py $(MKAPIFORMAT) -o $$@ -m $(1) $$(TDEFS) $(2)
|
||||
$$(BUILD)/$(1).pyi: $(TOP)/tools/mkapi.py $(filter-out -t,$(2)) $$(DEFS)
|
||||
$$(ECHO) "MKAPI $(1)"
|
||||
$$(Q)$$(MKDIR) -p $$(BUILD)
|
||||
$$(Q)$$(PYTHON) $(TOP)/tools/mkapi.py $(MKAPIFORMAT) --doc -o $$@ -m $(1) $$(TDEFS) $(2)
|
||||
SRC_C += $(BUILD)/mod$(1).c
|
||||
SRC_QSTR += $(BUILD)/mod$(1).c
|
||||
mkapi:: $$(BUILD)/mod$(1).c
|
||||
doc:: $$(BUILD)/$(1).pyi
|
||||
endef
|
||||
|
||||
SRC_C += extmod/multiverse_support.c
|
||||
|
|
|
|||
130
tools/mkapi.py
Normal file → Executable file
130
tools/mkapi.py
Normal file → Executable file
|
|
@ -1,3 +1,5 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
import pathlib
|
||||
import sys
|
||||
import subprocess
|
||||
|
|
@ -569,6 +571,8 @@ class Processor:
|
|||
else:
|
||||
if is_const:
|
||||
base_type = self.parse_type(self.remove_const(base_type))
|
||||
if isinstance(base_type, str):
|
||||
return f"const {base_type}"
|
||||
return dataclasses.replace(base_type, is_const=True)
|
||||
return typestr
|
||||
|
||||
|
|
@ -592,6 +596,91 @@ class Processor:
|
|||
if isinstance(d, FunPtr):
|
||||
self.funptrs.add(d.name)
|
||||
|
||||
def document(self, defs):
|
||||
self.body.append(f'"""{self.modname} - Wrapped MacOS APIs"""')
|
||||
self.body.append("import array")
|
||||
for d in defs:
|
||||
try:
|
||||
self.document_node(d)
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"failed to document {d}") from e
|
||||
|
||||
def pytype(self, ctype):
|
||||
if ctype is None:
|
||||
return None
|
||||
if ctype in {"LONGINT", "INTEGER"}:
|
||||
return "int"
|
||||
if ctype == "INTEGER*":
|
||||
return "array.array['h']"
|
||||
if ctype == "LONGINT*":
|
||||
return "array.array['i']"
|
||||
|
||||
parsed = self.parse_type(ctype)
|
||||
print("pytype", ctype, "->", repr(parsed))
|
||||
if isinstance(parsed, Ptr):
|
||||
return parsed.pointee
|
||||
return ctype
|
||||
|
||||
@singledispatchmethod
|
||||
def document_node(self, node):
|
||||
if type(node) in self.unknowns:
|
||||
return
|
||||
self.unknowns.add(type(node))
|
||||
raise RuntimeError(f"# Unknown {node!r:.68s}...")
|
||||
|
||||
@document_node.register
|
||||
def document_typedef(self, typedef: Typedef):
|
||||
print(typedef)
|
||||
|
||||
@document_node.register
|
||||
def document_struct(self, struct: Struct):
|
||||
self.body.append(f"class {struct.name}:")
|
||||
self.body.append(' """A uctypes structure"""')
|
||||
for m in struct.members:
|
||||
self.body.append(f' {m.name}: "{self.pytype(m.type)}"')
|
||||
|
||||
@document_node.register
|
||||
def document_union(self, union: Union):
|
||||
self.body.append(f"class {union.name}:")
|
||||
self.body.append(' """A uctypes union"""')
|
||||
for m in union.members:
|
||||
self.body.append(f' {m.name}: "{self.pytype(m.type)}"')
|
||||
|
||||
@document_node.register
|
||||
def document_pyverbatim(self, p: PyVerbatim):
|
||||
pass
|
||||
|
||||
@document_node.register
|
||||
def document_function(self, f: Function):
|
||||
self.body.append(f"def {f.name}(")
|
||||
for i, m in enumerate(f.args):
|
||||
argname = m.name or f"arg{i}"
|
||||
self.body.append(f' {argname}: "{self.pytype(m.type)}",')
|
||||
self.body.append(f") -> {self.pytype(f.return_)}: ...\n")
|
||||
|
||||
@document_node.register
|
||||
def document_enum(self, e: Enum):
|
||||
for m in e.values:
|
||||
self.body.append(f"{m.name} : int")
|
||||
|
||||
@document_node.register
|
||||
def document_lm(self, lm: LowMem):
|
||||
# TODO type of LMGet/Set
|
||||
self.body.append(f"def LMGet{lm.name}() -> object: ...")
|
||||
self.body.append(f"def LMSet{lm.name}(value) -> None: ...")
|
||||
|
||||
@document_node.register
|
||||
def document_dispatcher(self, d: Dispatcher):
|
||||
pass
|
||||
|
||||
@document_node.register
|
||||
def document_funptr(self, f: FunPtr):
|
||||
pass
|
||||
|
||||
@document_node.register
|
||||
def document_verbatim(self, lm: Verbatim):
|
||||
pass
|
||||
|
||||
def emit(self, defs):
|
||||
for d in defs:
|
||||
print("emit", id(d), d)
|
||||
|
|
@ -916,35 +1005,48 @@ class Processor:
|
|||
"-t", "--typedefs", multiple=True, type=click.Path(path_type=pathlib.Path, exists=True)
|
||||
)
|
||||
@click.option("-m", "--modname", type=str)
|
||||
@click.option("-d/-c", "--doc/--code", "do_doc", is_flag=True)
|
||||
@click.option("--format/-no-format", "do_format")
|
||||
def main(defs_files, output, modname, typedefs, do_format=False):
|
||||
def main(defs_files, output, modname, typedefs, do_format=False, do_doc=False):
|
||||
if modname is None:
|
||||
modname = defs_files[0].stem
|
||||
|
||||
if output is None:
|
||||
output = pathlib.Path(f"mod{modname}.c")
|
||||
if do_doc:
|
||||
output = pathlib.Path(f"{modname}.pyi")
|
||||
else:
|
||||
output = pathlib.Path(f"mod{modname}.c")
|
||||
processor = Processor(modname)
|
||||
|
||||
for t in typedefs:
|
||||
defs = load_defs(t)
|
||||
processor.typedefs(defs)
|
||||
if not do_doc:
|
||||
processor.typedefs(defs)
|
||||
|
||||
defs = []
|
||||
for f in defs_files:
|
||||
defs.extend(load_defs(f))
|
||||
processor.typedefs(defs)
|
||||
processor.emit(defs)
|
||||
if not do_doc:
|
||||
processor.typedefs(defs)
|
||||
|
||||
if do_format:
|
||||
tmpfile = output.with_suffix(".tmp.c")
|
||||
with open(tmpfile, "w") as f:
|
||||
processor.make_output(f)
|
||||
print(f"Formatting {output}: ", [script_dir / "codeformat.py", "-c", tmpfile])
|
||||
subprocess.check_call([script_dir / "codeformat.py", "-c", tmpfile])
|
||||
tmpfile.rename(output)
|
||||
else:
|
||||
if do_doc:
|
||||
processor.document(defs)
|
||||
with open(output, "w") as f:
|
||||
processor.make_output(f)
|
||||
for line in processor.body:
|
||||
print(line, file=f)
|
||||
else:
|
||||
processor.emit(defs)
|
||||
|
||||
if do_format:
|
||||
tmpfile = output.with_suffix(".tmp.c")
|
||||
with open(tmpfile, "w") as f:
|
||||
processor.make_output(f)
|
||||
print(f"Formatting {output}: ", [script_dir / "codeformat.py", "-c", tmpfile])
|
||||
subprocess.check_call([script_dir / "codeformat.py", "-c", tmpfile])
|
||||
tmpfile.rename(output)
|
||||
else:
|
||||
with open(output, "w") as f:
|
||||
processor.make_output(f)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
Loading…
Reference in a new issue