| #!/usr/bin/env python3 | |
| # | |
| # ====- Generate documentation for libc functions ------------*- python -*--==# | |
| # | |
| # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| # See https://llvm.org/LICENSE.txt for license information. | |
| # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| # | |
| # ==-------------------------------------------------------------------------==# | |
| from argparse import ArgumentParser, Namespace | |
| from pathlib import Path | |
| from typing import Dict | |
| import os | |
| import sys | |
| import yaml | |
| from header import Header | |
| class DocgenAPIFormatError(Exception): | |
| """Raised on fatal formatting errors with a description of a formatting error""" | |
| def check_api(header: Header, api: Dict): | |
| """ | |
| Checks that docgen yaml files are properly formatted. If there are any | |
| fatal formatting errors, raises exceptions with error messages useful for | |
| fixing formatting. Warnings are printed to stderr on non-fatal formatting | |
| errors. The code that runs after ``check_api(api)`` is called expects that | |
| ``check_api`` executed without raising formatting exceptions so the yaml | |
| matches the formatting specified here. | |
| The yaml file may contain: | |
| * an optional macros object | |
| * an optional functions object | |
| Formatting of ``macros`` and ``functions`` objects | |
| ================================================== | |
| If a macros or functions object is present, then it may contain nested | |
| objects. Each of these nested objects should have a name matching a macro | |
| or function's name, and each nested object must have the property: | |
| ``"c-definition"`` or ``"posix-definition"``. | |
| Description of properties | |
| ========================= | |
| The defined property is intended to be a reference to a part of the | |
| standard that defines the function or macro. For the ``"c-definition"`` property, | |
| this should be a C standard section number. For the ``"posix-definition"`` property, | |
| this should be a link to the definition. | |
| :param api: docgen yaml file contents parsed into a dict | |
| """ | |
| errors = [] | |
| # We require entries to have at least one of these. | |
| possible_keys = [ | |
| "c-definition", | |
| "in-latest-posix", | |
| "removed-in-posix-2008", | |
| "removed-in-posix-2024", | |
| ] | |
| # Validate macros | |
| if "macros" in api: | |
| if not header.macro_file_exists(): | |
| print( | |
| 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.", | |
| file=sys.stderr, | |
| ) | |
| macros = api["macros"] | |
| for name, obj in macros.items(): | |
| if not any(k in obj for k in possible_keys): | |
| err = f"error: Macro {name} does not contain at least one required property: {possible_keys}" | |
| errors.append(err) | |
| # Validate functions | |
| if "functions" in api: | |
| if not header.fns_dir_exists(): | |
| print( | |
| 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.", | |
| file=sys.stderr, | |
| ) | |
| fns = api["functions"] | |
| for name, obj in fns.items(): | |
| if not any(k in obj for k in possible_keys): | |
| err = f"error: function {name} does not contain at least one required property: {possible_keys}" | |
| errors.append(err) | |
| if errors: | |
| raise DocgenAPIFormatError("\n".join(errors)) | |
| def load_api(header: Header) -> Dict: | |
| api = header.docgen_yaml.read_text(encoding="utf-8") | |
| return yaml.safe_load(api) | |
| def print_tbl_dir(name): | |
| print( | |
| f""" | |
| .. list-table:: | |
| :widths: auto | |
| :align: center | |
| :header-rows: 1 | |
| * - {name} | |
| - Implemented | |
| - C23 Standard Section | |
| - POSIX Docs""" | |
| ) | |
| def print_functions_rst(header: Header, functions: Dict): | |
| tbl_hdr = "Functions" | |
| print(tbl_hdr) | |
| print("=" * len(tbl_hdr)) | |
| print_tbl_dir("Function") | |
| for name in sorted(functions.keys()): | |
| print(f" * - {name}") | |
| if header.fns_dir_exists() and header.implements_fn(name): | |
| print(" - |check|") | |
| else: | |
| print(" -") | |
| if "c-definition" in functions[name]: | |
| print(f' - {functions[name]["c-definition"]}') | |
| else: | |
| print(" -") | |
| if "in-latest-posix" in functions[name]: | |
| print( | |
| f" - `POSIX.1-2024 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/{name}.html>`__" | |
| ) | |
| elif "removed-in-posix-2008" in functions[name]: | |
| print( | |
| f" - `removed in POSIX.1-2008 <https://pubs.opengroup.org/onlinepubs/007904875/functions/{name}.html>`__" | |
| ) | |
| elif "removed-in-posix-2024" in functions[name]: | |
| print( | |
| f" - `removed in POSIX.1-2024 <https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/functions/{name}.html>`__" | |
| ) | |
| else: | |
| print(" -") | |
| def posix_basedefs_url(header: Header) -> str: | |
| """Return the Open Group basedefs URL for a header.""" | |
| posix_header_name = header.name.replace("/", "_") | |
| return f"https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/{posix_header_name}.html" | |
| def print_macros_rst(header: Header, macros: Dict): | |
| tbl_hdr = "Macros" | |
| print(tbl_hdr) | |
| print("=" * len(tbl_hdr)) | |
| print_tbl_dir("Macro") | |
| for name in sorted(macros.keys()): | |
| print(f" * - {name}") | |
| if header.macro_file_exists() and header.implements_macro(name): | |
| print(" - |check|") | |
| else: | |
| print(" -") | |
| if "c-definition" in macros[name]: | |
| print(f' - {macros[name]["c-definition"]}') | |
| else: | |
| print(" -") | |
| if "in-latest-posix" in macros[name]: | |
| print(f" - `POSIX.1-2024 <{posix_basedefs_url(header)}>`__") | |
| else: | |
| print(" -") | |
| print() | |
| def print_impl_status_rst(header: Header, api: Dict): | |
| print("=" * len(header.name)) | |
| print(header.name) | |
| print("=" * len(header.name)) | |
| print() | |
| # the macro and function sections are both optional | |
| if "macros" in api: | |
| print_macros_rst(header, api["macros"]) | |
| if "functions" in api: | |
| print_functions_rst(header, api["functions"]) | |
| # This code implicitly relies on docgen.py being in the same dir as the yaml | |
| # files and is likely to need to be fixed when re-integrating docgen into | |
| # hdrgen. | |
| def get_choices() -> list: | |
| choices = [] | |
| for path in Path(__file__).parent.rglob("*.yaml"): | |
| fname = path.with_suffix(".h").name | |
| if path.parent != Path(__file__).parent: | |
| fname = path.parent.name + os.sep + fname | |
| choices.append(fname) | |
| return choices | |
| def parse_args() -> Namespace: | |
| parser = ArgumentParser() | |
| parser.add_argument("header_name", choices=get_choices()) | |
| return parser.parse_args() | |
| if __name__ == "__main__": | |
| args = parse_args() | |
| header = Header(args.header_name) | |
| api = load_api(header) | |
| check_api(header, api) | |
| print_impl_status_rst(header, api) |