Skip to main content
  • Home
  • Development
  • Documentation
  • Donate
  • Operational login
  • Browse the archive

swh logo
SoftwareHeritage
Software
Heritage
Archive
Features
  • Search

  • Downloads

  • Save code now

  • Add forge now

  • Help

Revision b4df039c6fe478297e532720e76d1213022410d5 authored by Jesper Nielsen on 26 October 2022, 08:27:38 UTC, committed by GitHub on 26 October 2022, 08:27:38 UTC
Fix mypy error. (#2009)
1 parent dc84ca2
  • Files
  • Changes
  • 03384ba
  • /
  • doc
  • /
  • generate_module_rst.py
Raw File Download
Permalinks

To reference or cite the objects present in the Software Heritage archive, permalinks based on SoftWare Hash IDentifiers (SWHIDs) must be used.
Select below a type of object currently browsed in order to display its associated SWHID and permalink.

  • revision
  • directory
  • content
revision badge
swh:1:rev:b4df039c6fe478297e532720e76d1213022410d5
directory badge Iframe embedding
swh:1:dir:85a70a7795ff96382e80b8173ea324b9cf40d9ed
content badge Iframe embedding
swh:1:cnt:e38966975308cc49945c3ee3000733b080848061
Citations

This interface enables to generate software citations, provided that the root directory of browsed objects contains a citation.cff or codemeta.json file.
Select below a type of object currently browsed in order to generate citations for them.

  • revision
  • directory
  • content
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
generate_module_rst.py
# Copyright 2019 GPflow Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Script to autogenerate .rst files for autodocumentation of classes and modules in GPflow.
To be run by the CI system to update docs.
"""
import inspect
from dataclasses import dataclass
from pathlib import Path
from types import ModuleType
from typing import Any, Callable, Deque, Dict, List, Mapping, Set, TextIO, Type, Union

from gpflow.utilities import Dispatcher

RST_LEVEL_SYMBOLS = ["=", "-", "~", '"', "'", "^"]

IGNORE_MODULES = {
    "gpflow.covariances.dispatch",
    "gpflow.conditionals.dispatch",
    "gpflow.expectations.dispatch",
    "gpflow.kullback_leiblers.dispatch",
    "gpflow.versions",
}


def _header(header: str, level: int) -> str:
    return f"{header}\n{RST_LEVEL_SYMBOLS[level] * len(header)}"


@dataclass
class DocumentableDispatcher:

    name: str
    obj: Dispatcher

    def implementations(self) -> Mapping[Callable[..., Any], List[Type[Any]]]:
        implementations: Dict[Callable[..., Any], List[Type[Any]]] = {}
        for args, impl in self.obj.funcs.items():
            implementations.setdefault(impl, []).append(args)
        return implementations

    def write(self, out: TextIO) -> None:
        out.write(
            f"""
{_header(self.name, 2)}

This function uses multiple dispatch, which will depend on the type of argument passed in:
"""
        )
        for impl, argss in self.implementations().items():
            impl_name = f"{impl.__module__}.{impl.__name__}"

            out.write(
                """
.. code-block:: python

"""
            )
            for args in argss:
                arg_names = ", ".join([a.__name__ for a in args])
                out.write(f"    {self.name}( {arg_names} )\n")
            out.write(f"    # dispatch to -> {impl_name}(...)\n")
            out.write(
                f"""
.. autofunction:: {impl_name}
"""
            )


@dataclass
class DocumentableClass:

    name: str
    obj: Type[Any]

    def write(self, out: TextIO) -> None:
        out.write(
            f"""
{_header(self.name, 2)}

.. autoclass:: {self.name}
   :show-inheritance:
   :members:
"""
        )


@dataclass
class DocumentableFunction:

    name: str
    obj: Callable[..., Any]

    def write(self, out: TextIO) -> None:
        out.write(
            f"""
{_header(self.name, 2)}

.. autofunction:: {self.name}
"""
        )


@dataclass
class DocumentableModule:

    name: str
    obj: ModuleType
    modules: List["DocumentableModule"]
    classes: List[DocumentableClass]
    functions: List[Union[DocumentableDispatcher, DocumentableFunction]]

    @staticmethod
    def collect(
        root: ModuleType,
    ) -> "DocumentableModule":
        root_name = root.__name__
        exported_names = set(getattr(root, "__all__", []))

        modules: List["DocumentableModule"] = []
        classes: List[DocumentableClass] = []
        functions: List[Union[DocumentableDispatcher, DocumentableFunction]] = []

        for key in dir(root):
            if key.startswith("_"):
                continue

            child = getattr(root, key)
            child_name = root_name + "." + key
            if child_name in IGNORE_MODULES:
                continue

            # pylint: disable=cell-var-from-loop
            def _should_ignore(child: Union[Callable[..., Any], Type[Any]]) -> bool:
                declared_in_root = child.__module__ == root_name
                explicitly_exported = key in exported_names
                return not (declared_in_root or explicitly_exported)

            # pylint: enable=cell-var-from-loop

            if isinstance(child, Dispatcher):
                functions.append(DocumentableDispatcher(child_name, child))
            elif inspect.ismodule(child):
                if child.__name__ != child_name:  # Ignore imports of modules.
                    continue
                modules.append(DocumentableModule.collect(child))
            elif inspect.isclass(child):
                if _should_ignore(child):
                    continue
                classes.append(DocumentableClass(child_name, child))
            elif inspect.isfunction(child):
                if _should_ignore(child):
                    continue
                functions.append(DocumentableFunction(child_name, child))

        return DocumentableModule(root_name, root, modules, classes, functions)

    def seen_in_dispatchers(self, seen: Set[int]) -> None:
        for module in self.modules:
            module.seen_in_dispatchers(seen)
        for function in self.functions:
            if isinstance(function, DocumentableDispatcher):
                impls = function.obj.funcs.values()
                for impl in impls:
                    seen.add(id(impl))

    def prune_duplicates(self) -> None:
        seen: Set[int] = set()
        self.seen_in_dispatchers(seen)

        # Breadth-first search so that we prefer objects with shorter names.
        todo = Deque([self])
        while todo:
            module = todo.popleft()

            new_classes = []
            for c in module.classes:
                if id(c.obj) not in seen:
                    seen.add(id(c.obj))
                    new_classes.append(c)
            module.classes = new_classes

            new_functions = []
            for f in module.functions:
                if id(f.obj) not in seen:
                    seen.add(id(f.obj))
                    new_functions.append(f)
            module.functions = new_functions

            todo.extend(module.modules)

    def prune_empty_modules(self) -> None:
        new_modules = []
        for m in self.modules:
            m.prune_empty_modules()

            if m.modules or m.classes or m.functions:
                new_modules.append(m)
        self.modules = new_modules

    def prune(self) -> None:
        self.prune_duplicates()
        self.prune_empty_modules()

    def write_modules(self, out: TextIO) -> None:
        if not self.modules:
            return

        out.write(
            f"""
{_header('Modules', 1)}

.. toctree::
   :maxdepth: 1

"""
        )
        for module in self.modules:
            out.write(f"   {module.name} <{module.name.split('.')[-1]}/index>\n")

    def write_classes(self, out: TextIO) -> None:
        if not self.classes:
            return

        out.write(
            f"""
{_header('Classes', 1)}
"""
        )
        for cls in self.classes:
            cls.write(out)

    def write_functions(self, out: TextIO) -> None:
        if not self.functions:
            return

        out.write(
            f"""
{_header('Functions', 1)}
"""
        )
        for function in self.functions:
            function.write(out)

    def write(self, path: Path) -> None:
        dir_path = path / f"{self.name.replace('.', '/')}"
        dir_path.mkdir(parents=True, exist_ok=True)
        index_path = dir_path / "index.rst"
        with index_path.open("wt") as out:
            print("Writing", index_path)
            out.write(
                f"""{_header(self.name, 0)}

.. THIS IS AN AUTOGENERATED RST FILE

.. automodule:: {self.name}
"""
            )
            self.write_modules(out)
            self.write_classes(out)
            self.write_functions(out)

        for module in self.modules:
            module.write(path)

    def str_into(self, indent: int, lines: List[str]) -> None:
        lines.append(2 * indent * " " + "Module: " + self.name)
        for module in self.modules:
            module.str_into(indent + 1, lines)
        for cls in self.classes:
            lines.append(2 * (indent + 1) * " " + "Class: " + cls.name)
        for function in self.functions:
            lines.append(2 * (indent + 1) * " " + "Function: " + function.name)

    def __str__(self) -> str:
        lines: List[str] = []
        self.str_into(0, lines)
        return "\n".join(lines)


def generate_module_rst(module: ModuleType, dest: Path) -> None:
    """
    Traverses the given `module` and generates `.rst` files for Sphinx.
    """
    docs = DocumentableModule.collect(module)
    docs.prune()
    docs.write(dest)
The diff you're trying to view is too large. Only the first 1000 changed files have been loaded.
Showing with 0 additions and 0 deletions (0 / 0 diffs computed)
swh spinner

Computing file changes ...

Software Heritage — Copyright (C) 2015–2025, The Software Heritage developers. License: GNU AGPLv3+.
The source code of Software Heritage itself is available on our development forge.
The source code files archived by Software Heritage are available under their own copyright and licenses.
Terms of use: Archive access, API— Contact— JavaScript license information— Web API

back to top