xref: /llvm-project/libc/utils/docgen/docgen.py (revision 88bcf7283b35b979ace0c6be32736b13f6b771ae)
1af34a5d3SNick Desaulniers#!/usr/bin/env python
2af34a5d3SNick Desaulniers#
3af34a5d3SNick Desaulniers# ====- Generate documentation for libc functions  ------------*- python -*--==#
4af34a5d3SNick Desaulniers#
5af34a5d3SNick Desaulniers# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
6af34a5d3SNick Desaulniers# See https://llvm.org/LICENSE.txt for license information.
7af34a5d3SNick Desaulniers# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
8af34a5d3SNick Desaulniers#
9af34a5d3SNick Desaulniers# ==-------------------------------------------------------------------------==#
10af34a5d3SNick Desaulniersfrom argparse import ArgumentParser, Namespace
11af34a5d3SNick Desaulniersfrom pathlib import Path
12af34a5d3SNick Desaulniersfrom typing import Dict
13e17d2b58SNick Desaulniersimport os
14e17d2b58SNick Desaulniersimport sys
15*88bcf728SNick Desaulniersimport yaml
16af34a5d3SNick Desaulniers
170f6c4d8bSMichael Flandersfrom header import Header
18af34a5d3SNick Desaulniers
190f6c4d8bSMichael Flanders
200f6c4d8bSMichael Flandersclass DocgenAPIFormatError(Exception):
210f6c4d8bSMichael Flanders    """Raised on fatal formatting errors with a description of a formatting error"""
220f6c4d8bSMichael Flanders
230f6c4d8bSMichael Flanders
240f6c4d8bSMichael Flandersdef check_api(header: Header, api: Dict):
250f6c4d8bSMichael Flanders    """
26*88bcf728SNick Desaulniers    Checks that docgen yaml files are properly formatted. If there are any
270f6c4d8bSMichael Flanders    fatal formatting errors, raises exceptions with error messages useful for
280f6c4d8bSMichael Flanders    fixing formatting. Warnings are printed to stderr on non-fatal formatting
290f6c4d8bSMichael Flanders    errors. The code that runs after ``check_api(api)`` is called expects that
30*88bcf728SNick Desaulniers    ``check_api`` executed without raising formatting exceptions so the yaml
310f6c4d8bSMichael Flanders    matches the formatting specified here.
320f6c4d8bSMichael Flanders
33*88bcf728SNick Desaulniers    The yaml file may contain:
340f6c4d8bSMichael Flanders    * an optional macros object
350f6c4d8bSMichael Flanders    * an optional functions object
360f6c4d8bSMichael Flanders
370f6c4d8bSMichael Flanders    Formatting of ``macros`` and ``functions`` objects
380f6c4d8bSMichael Flanders    ==================================================
390f6c4d8bSMichael Flanders
400f6c4d8bSMichael Flanders    If a macros or functions object is present, then it may contain nested
410f6c4d8bSMichael Flanders    objects. Each of these nested objects should have a name matching a macro
420f6c4d8bSMichael Flanders    or function's name, and each nested object must have the property:
430f6c4d8bSMichael Flanders    ``"c-definition"`` or ``"posix-definition"``.
440f6c4d8bSMichael Flanders
450f6c4d8bSMichael Flanders    Description of properties
460f6c4d8bSMichael Flanders    =========================
470f6c4d8bSMichael Flanders    The defined property is intended to be a reference to a part of the
480f6c4d8bSMichael Flanders    standard that defines the function or macro. For the ``"c-definition"`` property,
490f6c4d8bSMichael Flanders    this should be a C standard section number. For the ``"posix-definition"`` property,
500f6c4d8bSMichael Flanders    this should be a link to the definition.
510f6c4d8bSMichael Flanders
52*88bcf728SNick Desaulniers    :param api: docgen yaml file contents parsed into a dict
530f6c4d8bSMichael Flanders    """
540f6c4d8bSMichael Flanders    errors = []
55c047a5b3SNick Desaulniers    # We require entries to have at least one of these.
56c047a5b3SNick Desaulniers    possible_keys = [
57c047a5b3SNick Desaulniers        "c-definition",
58c047a5b3SNick Desaulniers        "in-latest-posix",
59c047a5b3SNick Desaulniers        "removed-in-posix-2008",
60c047a5b3SNick Desaulniers    ]
610f6c4d8bSMichael Flanders
620f6c4d8bSMichael Flanders    # Validate macros
630f6c4d8bSMichael Flanders    if "macros" in api:
640f6c4d8bSMichael Flanders        if not header.macro_file_exists():
650f6c4d8bSMichael Flanders            print(
660f6c4d8bSMichael Flanders                f"warning: Macro definitions are listed for {header.name}, but no macro file can be found in the directory tree rooted at {header.macros_dir}. All macros will be listed as not implemented.",
670f6c4d8bSMichael Flanders                file=sys.stderr,
680f6c4d8bSMichael Flanders            )
690f6c4d8bSMichael Flanders
700f6c4d8bSMichael Flanders        macros = api["macros"]
710f6c4d8bSMichael Flanders
720f6c4d8bSMichael Flanders        for name, obj in macros.items():
73c047a5b3SNick Desaulniers            if not any(k in obj for k in possible_keys):
74c047a5b3SNick Desaulniers                err = f"error: Macro {name} does not contain at least one required property: {possible_keys}"
750f6c4d8bSMichael Flanders                errors.append(err)
760f6c4d8bSMichael Flanders
770f6c4d8bSMichael Flanders    # Validate functions
780f6c4d8bSMichael Flanders    if "functions" in api:
790f6c4d8bSMichael Flanders        if not header.fns_dir_exists():
800f6c4d8bSMichael Flanders            print(
810f6c4d8bSMichael Flanders                f"warning: Function definitions are listed for {header.name}, but no function implementation directory exists at {header.fns_dir}. All functions will be listed as not implemented.",
820f6c4d8bSMichael Flanders                file=sys.stderr,
830f6c4d8bSMichael Flanders            )
840f6c4d8bSMichael Flanders
850f6c4d8bSMichael Flanders        fns = api["functions"]
860f6c4d8bSMichael Flanders        for name, obj in fns.items():
87c047a5b3SNick Desaulniers            if not any(k in obj for k in possible_keys):
88c047a5b3SNick Desaulniers                err = f"error: function {name} does not contain at least one required property: {possible_keys}"
890f6c4d8bSMichael Flanders                errors.append(err)
900f6c4d8bSMichael Flanders
910f6c4d8bSMichael Flanders    if errors:
920f6c4d8bSMichael Flanders        raise DocgenAPIFormatError("\n".join(errors))
930f6c4d8bSMichael Flanders
940f6c4d8bSMichael Flanders
950f6c4d8bSMichael Flandersdef load_api(header: Header) -> Dict:
96*88bcf728SNick Desaulniers    api = header.docgen_yaml.read_text(encoding="utf-8")
97*88bcf728SNick Desaulniers    return yaml.safe_load(api)
98af34a5d3SNick Desaulniers
99af34a5d3SNick Desaulniers
100659834dfSNick Desaulniersdef print_tbl_dir(name):
101af34a5d3SNick Desaulniers    print(
102af34a5d3SNick Desaulniers        f"""
103af34a5d3SNick Desaulniers.. list-table::
104af34a5d3SNick Desaulniers  :widths: auto
105af34a5d3SNick Desaulniers  :align: center
106af34a5d3SNick Desaulniers  :header-rows: 1
107af34a5d3SNick Desaulniers
108659834dfSNick Desaulniers  * - {name}
109af34a5d3SNick Desaulniers    - Implemented
1100f6c4d8bSMichael Flanders    - C23 Standard Section
111c047a5b3SNick Desaulniers    - POSIX Docs"""
112af34a5d3SNick Desaulniers    )
1130f6c4d8bSMichael Flanders
1140f6c4d8bSMichael Flanders
1150f6c4d8bSMichael Flandersdef print_functions_rst(header: Header, functions: Dict):
1160f6c4d8bSMichael Flanders    tbl_hdr = "Functions"
1170f6c4d8bSMichael Flanders    print(tbl_hdr)
1180f6c4d8bSMichael Flanders    print("=" * len(tbl_hdr))
1190f6c4d8bSMichael Flanders
120659834dfSNick Desaulniers    print_tbl_dir("Function")
1210f6c4d8bSMichael Flanders
1220f6c4d8bSMichael Flanders    for name in sorted(functions.keys()):
1230f6c4d8bSMichael Flanders        print(f"  * - {name}")
1240f6c4d8bSMichael Flanders
1250f6c4d8bSMichael Flanders        if header.fns_dir_exists() and header.implements_fn(name):
1260f6c4d8bSMichael Flanders            print("    - |check|")
1270f6c4d8bSMichael Flanders        else:
1280f6c4d8bSMichael Flanders            print("    -")
1290f6c4d8bSMichael Flanders
1300f6c4d8bSMichael Flanders        if "c-definition" in functions[name]:
1310f6c4d8bSMichael Flanders            print(f'    - {functions[name]["c-definition"]}')
1320f6c4d8bSMichael Flanders        else:
1330f6c4d8bSMichael Flanders            print("    -")
1340f6c4d8bSMichael Flanders
135c047a5b3SNick Desaulniers        if "in-latest-posix" in functions[name]:
136c047a5b3SNick Desaulniers            print(
137c047a5b3SNick Desaulniers                f"    - `POSIX.1-2024 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/{name}.html>`__"
138c047a5b3SNick Desaulniers            )
139c047a5b3SNick Desaulniers        elif "removed-in-posix-2008" in functions[name]:
140c047a5b3SNick Desaulniers            print(
141c047a5b3SNick Desaulniers                f"    - `removed in POSIX.1-2008 <https://pubs.opengroup.org/onlinepubs/007904875/functions/{name}.html>`__"
142c047a5b3SNick Desaulniers            )
1430f6c4d8bSMichael Flanders        else:
1440f6c4d8bSMichael Flanders            print("    -")
1450f6c4d8bSMichael Flanders
1460f6c4d8bSMichael Flanders
1470f6c4d8bSMichael Flandersdef print_macros_rst(header: Header, macros: Dict):
1480f6c4d8bSMichael Flanders    tbl_hdr = "Macros"
1490f6c4d8bSMichael Flanders    print(tbl_hdr)
1500f6c4d8bSMichael Flanders    print("=" * len(tbl_hdr))
1510f6c4d8bSMichael Flanders
152659834dfSNick Desaulniers    print_tbl_dir("Macro")
1530f6c4d8bSMichael Flanders
1540f6c4d8bSMichael Flanders    for name in sorted(macros.keys()):
1550f6c4d8bSMichael Flanders        print(f"  * - {name}")
1560f6c4d8bSMichael Flanders
1570f6c4d8bSMichael Flanders        if header.macro_file_exists() and header.implements_macro(name):
1580f6c4d8bSMichael Flanders            print("    - |check|")
1590f6c4d8bSMichael Flanders        else:
1600f6c4d8bSMichael Flanders            print("    -")
1610f6c4d8bSMichael Flanders
1620f6c4d8bSMichael Flanders        if "c-definition" in macros[name]:
1630f6c4d8bSMichael Flanders            print(f'    - {macros[name]["c-definition"]}')
1640f6c4d8bSMichael Flanders        else:
1650f6c4d8bSMichael Flanders            print("    -")
1660f6c4d8bSMichael Flanders
167c047a5b3SNick Desaulniers        if "in-latest-posix" in macros[name]:
168c047a5b3SNick Desaulniers            print(
169c047a5b3SNick Desaulniers                f"    - `POSIX.1-2024 <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/{header.name}.html>`__"
170c047a5b3SNick Desaulniers            )
1710f6c4d8bSMichael Flanders        else:
1720f6c4d8bSMichael Flanders            print("    -")
1730f6c4d8bSMichael Flanders    print()
1740f6c4d8bSMichael Flanders
1750f6c4d8bSMichael Flanders
1760f6c4d8bSMichael Flandersdef print_impl_status_rst(header: Header, api: Dict):
177e17d2b58SNick Desaulniers    if os.sep in header.name:
178e17d2b58SNick Desaulniers        print(".. include:: ../../check.rst\n")
179e17d2b58SNick Desaulniers    else:
180a9aff440SNick Desaulniers        print(".. include:: ../check.rst\n")
1810f6c4d8bSMichael Flanders
1820f6c4d8bSMichael Flanders    print("=" * len(header.name))
1830f6c4d8bSMichael Flanders    print(header.name)
1840f6c4d8bSMichael Flanders    print("=" * len(header.name))
1850f6c4d8bSMichael Flanders    print()
1860f6c4d8bSMichael Flanders
1870f6c4d8bSMichael Flanders    # the macro and function sections are both optional
1880f6c4d8bSMichael Flanders    if "macros" in api:
1890f6c4d8bSMichael Flanders        print_macros_rst(header, api["macros"])
1900f6c4d8bSMichael Flanders
1910f6c4d8bSMichael Flanders    if "functions" in api:
1920f6c4d8bSMichael Flanders        print_functions_rst(header, api["functions"])
193af34a5d3SNick Desaulniers
194af34a5d3SNick Desaulniers
195*88bcf728SNick Desaulniers# This code implicitly relies on docgen.py being in the same dir as the yaml
196e17d2b58SNick Desaulniers# files and is likely to need to be fixed when re-integrating docgen into
197e17d2b58SNick Desaulniers# hdrgen.
19852db9038SNick Desaulniersdef get_choices() -> list:
199e17d2b58SNick Desaulniers    choices = []
200*88bcf728SNick Desaulniers    for path in Path(__file__).parent.rglob("*.yaml"):
201e17d2b58SNick Desaulniers        fname = path.with_suffix(".h").name
202e17d2b58SNick Desaulniers        if path.parent != Path(__file__).parent:
203e17d2b58SNick Desaulniers            fname = path.parent.name + os.sep + fname
204e17d2b58SNick Desaulniers        choices.append(fname)
205e17d2b58SNick Desaulniers    return choices
206e17d2b58SNick Desaulniers
207e17d2b58SNick Desaulniers
208af34a5d3SNick Desaulniersdef parse_args() -> Namespace:
209af34a5d3SNick Desaulniers    parser = ArgumentParser()
210e17d2b58SNick Desaulniers    parser.add_argument("header_name", choices=get_choices())
211af34a5d3SNick Desaulniers    return parser.parse_args()
212af34a5d3SNick Desaulniers
213af34a5d3SNick Desaulniers
214af34a5d3SNick Desaulniersif __name__ == "__main__":
215af34a5d3SNick Desaulniers    args = parse_args()
2160f6c4d8bSMichael Flanders    header = Header(args.header_name)
2170f6c4d8bSMichael Flanders    api = load_api(header)
2180f6c4d8bSMichael Flanders    check_api(header, api)
219af34a5d3SNick Desaulniers
2200f6c4d8bSMichael Flanders    print_impl_status_rst(header, api)
221