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