|
| 1 | +# License: MIT |
| 2 | +# Copyright © 2022 Frequenz Energy-as-a-Service GmbH |
| 3 | + |
| 4 | +"""Generate the code reference pages. |
| 5 | +
|
| 6 | +It uses the following `mkdocs` plugins: |
| 7 | +
|
| 8 | +* `mkdocs-gen-files` to generate the API documentation pages. |
| 9 | +* `mkdocs-literate-nav` to make use of the generate `SUMMARY.md` file. |
| 10 | +
|
| 11 | +Based on the recipe at: |
| 12 | +https://mkdocstrings.github.io/recipes/#automatic-code-reference-pages |
| 13 | +""" |
| 14 | + |
| 15 | +from pathlib import Path |
| 16 | +from typing import Tuple |
| 17 | + |
| 18 | +import mkdocs_gen_files |
| 19 | + |
| 20 | + |
| 21 | +def _is_internal(path_parts: Tuple[str, ...]) -> bool: |
| 22 | + """Tell if the path is internal judging by the parts. |
| 23 | +
|
| 24 | + Args: |
| 25 | + path_parts: Path.parts of the path to check. |
| 26 | +
|
| 27 | + Returns: |
| 28 | + True if the path is internal. |
| 29 | + """ |
| 30 | + |
| 31 | + def with_underscore_not_init(part: str) -> bool: |
| 32 | + return part.startswith("_") and part != "__init__" |
| 33 | + |
| 34 | + return any(p for p in path_parts if with_underscore_not_init(p)) |
| 35 | + |
| 36 | + |
| 37 | +def generate_api_pages(src_path: str = "src", dst_path: str = "reference") -> None: |
| 38 | + """Generate API documentation pages for the code. |
| 39 | +
|
| 40 | + Internal modules (those starting with an underscore except from `__init__`) are |
| 41 | + not included. |
| 42 | +
|
| 43 | + A summary page is generated as `SUMMARY.md` which is compatible with the |
| 44 | + `mkdocs-literary-nav` plugin. |
| 45 | +
|
| 46 | + Args: |
| 47 | + src_path: Path where the code is located. |
| 48 | + dst_path: Path where the documentation should be generated. This is relative |
| 49 | + to the output directory of mkdocs. |
| 50 | + """ |
| 51 | + # type ignore because mkdocs_gen_files uses a very weird module-level |
| 52 | + # __getattr__() which messes up the type system |
| 53 | + nav = mkdocs_gen_files.Nav() # type: ignore |
| 54 | + |
| 55 | + for path in sorted(Path(src_path).rglob("*.py")): |
| 56 | + module_path = path.relative_to(src_path).with_suffix("") |
| 57 | + |
| 58 | + doc_path = path.relative_to(src_path).with_suffix(".md") |
| 59 | + full_doc_path = Path(dst_path, doc_path) |
| 60 | + parts = tuple(module_path.parts) |
| 61 | + if _is_internal(parts): |
| 62 | + continue |
| 63 | + if parts[-1] == "__init__": |
| 64 | + doc_path = doc_path.with_name("index.md") |
| 65 | + full_doc_path = full_doc_path.with_name("index.md") |
| 66 | + parts = parts[:-1] |
| 67 | + |
| 68 | + nav[parts] = doc_path.as_posix() |
| 69 | + |
| 70 | + with mkdocs_gen_files.open(full_doc_path, "w") as output_file: |
| 71 | + output_file.write(f"::: {'.'.join(parts)}\n") |
| 72 | + |
| 73 | + mkdocs_gen_files.set_edit_path(full_doc_path, Path("..") / path) |
| 74 | + |
| 75 | + with mkdocs_gen_files.open(Path(dst_path) / "SUMMARY.md", "w") as nav_file: |
| 76 | + nav_file.writelines(nav.build_literate_nav()) |
0 commit comments