1#!/usr/bin/env python 2# 3# ====- Generate documentation for libc functions ------------*- python -*--==# 4# 5# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 6# See https://llvm.org/LICENSE.txt for license information. 7# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 8# 9# ==-------------------------------------------------------------------------==# 10from argparse import ArgumentParser, Namespace 11from pathlib import Path 12from typing import Dict 13import os 14import sys 15import yaml 16 17from header import Header 18 19 20class DocgenAPIFormatError(Exception): 21 """Raised on fatal formatting errors with a description of a formatting error""" 22 23 24def check_api(header: Header, api: Dict): 25 """ 26 Checks that docgen yaml files are properly formatted. If there are any 27 fatal formatting errors, raises exceptions with error messages useful for 28 fixing formatting. Warnings are printed to stderr on non-fatal formatting 29 errors. The code that runs after ``check_api(api)`` is called expects that 30 ``check_api`` executed without raising formatting exceptions so the yaml 31 matches the formatting specified here. 32 33 The yaml file may contain: 34 * an optional macros object 35 * an optional functions object 36 37 Formatting of ``macros`` and ``functions`` objects 38 ================================================== 39 40 If a macros or functions object is present, then it may contain nested 41 objects. Each of these nested objects should have a name matching a macro 42 or function's name, and each nested object must have the property: 43 ``"c-definition"`` or ``"posix-definition"``. 44 45 Description of properties 46 ========================= 47 The defined property is intended to be a reference to a part of the 48 standard that defines the function or macro. For the ``"c-definition"`` property, 49 this should be a C standard section number. For the ``"posix-definition"`` property, 50 this should be a link to the definition. 51 52 :param api: docgen yaml file contents parsed into a dict 53 """ 54 errors = [] 55 # We require entries to have at least one of these. 56 possible_keys = [ 57 "c-definition", 58 "in-latest-posix", 59 "removed-in-posix-2008", 60 ] 61 62 # Validate macros 63 if "macros" in api: 64 if not header.macro_file_exists(): 65 print( 66 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.", 67 file=sys.stderr, 68 ) 69 70 macros = api["macros"] 71 72 for name, obj in macros.items(): 73 if not any(k in obj for k in possible_keys): 74 err = f"error: Macro {name} does not contain at least one required property: {possible_keys}" 75 errors.append(err) 76 77 # Validate functions 78 if "functions" in api: 79 if not header.fns_dir_exists(): 80 print( 81 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.", 82 file=sys.stderr, 83 ) 84 85 fns = api["functions"] 86 for name, obj in fns.items(): 87 if not any(k in obj for k in possible_keys): 88 err = f"error: function {name} does not contain at least one required property: {possible_keys}" 89 errors.append(err) 90 91 if errors: 92 raise DocgenAPIFormatError("\n".join(errors)) 93 94 95def load_api(header: Header) -> Dict: 96 api = header.docgen_yaml.read_text(encoding="utf-8") 97 return yaml.safe_load(api) 98 99 100def print_tbl_dir(name): 101 print( 102 f""" 103.. list-table:: 104 :widths: auto 105 :align: center 106 :header-rows: 1 107 108 * - {name} 109 - Implemented 110 - C23 Standard Section 111 - POSIX Docs""" 112 ) 113 114 115def print_functions_rst(header: Header, functions: Dict): 116 tbl_hdr = "Functions" 117 print(tbl_hdr) 118 print("=" * len(tbl_hdr)) 119 120 print_tbl_dir("Function") 121 122 for name in sorted(functions.keys()): 123 print(f" * - {name}") 124 125 if header.fns_dir_exists() and header.implements_fn(name): 126 print(" - |check|") 127 else: 128 print(" -") 129 130 if "c-definition" in functions[name]: 131 print(f' - {functions[name]["c-definition"]}') 132 else: 133 print(" -") 134 135 if "in-latest-posix" in functions[name]: 136 print( 137 f" - `POSIX.1-2024 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/{name}.html>`__" 138 ) 139 elif "removed-in-posix-2008" in functions[name]: 140 print( 141 f" - `removed in POSIX.1-2008 <https://pubs.opengroup.org/onlinepubs/007904875/functions/{name}.html>`__" 142 ) 143 else: 144 print(" -") 145 146 147def print_macros_rst(header: Header, macros: Dict): 148 tbl_hdr = "Macros" 149 print(tbl_hdr) 150 print("=" * len(tbl_hdr)) 151 152 print_tbl_dir("Macro") 153 154 for name in sorted(macros.keys()): 155 print(f" * - {name}") 156 157 if header.macro_file_exists() and header.implements_macro(name): 158 print(" - |check|") 159 else: 160 print(" -") 161 162 if "c-definition" in macros[name]: 163 print(f' - {macros[name]["c-definition"]}') 164 else: 165 print(" -") 166 167 if "in-latest-posix" in macros[name]: 168 print( 169 f" - `POSIX.1-2024 <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/{header.name}.html>`__" 170 ) 171 else: 172 print(" -") 173 print() 174 175 176def print_impl_status_rst(header: Header, api: Dict): 177 if os.sep in header.name: 178 print(".. include:: ../../check.rst\n") 179 else: 180 print(".. include:: ../check.rst\n") 181 182 print("=" * len(header.name)) 183 print(header.name) 184 print("=" * len(header.name)) 185 print() 186 187 # the macro and function sections are both optional 188 if "macros" in api: 189 print_macros_rst(header, api["macros"]) 190 191 if "functions" in api: 192 print_functions_rst(header, api["functions"]) 193 194 195# This code implicitly relies on docgen.py being in the same dir as the yaml 196# files and is likely to need to be fixed when re-integrating docgen into 197# hdrgen. 198def get_choices() -> list: 199 choices = [] 200 for path in Path(__file__).parent.rglob("*.yaml"): 201 fname = path.with_suffix(".h").name 202 if path.parent != Path(__file__).parent: 203 fname = path.parent.name + os.sep + fname 204 choices.append(fname) 205 return choices 206 207 208def parse_args() -> Namespace: 209 parser = ArgumentParser() 210 parser.add_argument("header_name", choices=get_choices()) 211 return parser.parse_args() 212 213 214if __name__ == "__main__": 215 args = parse_args() 216 header = Header(args.header_name) 217 api = load_api(header) 218 check_api(header, api) 219 220 print_impl_status_rst(header, api) 221