226 lines · python
1#!/usr/bin/env python2#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-exception8#9# ==-------------------------------------------------------------------------==#10from argparse import ArgumentParser, Namespace11from pathlib import Path12from typing import Dict13import os14import sys15import yaml16 17from header import Header18 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 any27 fatal formatting errors, raises exceptions with error messages useful for28 fixing formatting. Warnings are printed to stderr on non-fatal formatting29 errors. The code that runs after ``check_api(api)`` is called expects that30 ``check_api`` executed without raising formatting exceptions so the yaml31 matches the formatting specified here.32 33 The yaml file may contain:34 * an optional macros object35 * an optional functions object36 37 Formatting of ``macros`` and ``functions`` objects38 ==================================================39 40 If a macros or functions object is present, then it may contain nested41 objects. Each of these nested objects should have a name matching a macro42 or function's name, and each nested object must have the property:43 ``"c-definition"`` or ``"posix-definition"``.44 45 Description of properties46 =========================47 The defined property is intended to be a reference to a part of the48 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 dict53 """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 "removed-in-posix-2024",61 ]62 63 # Validate macros64 if "macros" in api:65 if not header.macro_file_exists():66 print(67 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.",68 file=sys.stderr,69 )70 71 macros = api["macros"]72 73 for name, obj in macros.items():74 if not any(k in obj for k in possible_keys):75 err = f"error: Macro {name} does not contain at least one required property: {possible_keys}"76 errors.append(err)77 78 # Validate functions79 if "functions" in api:80 if not header.fns_dir_exists():81 print(82 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.",83 file=sys.stderr,84 )85 86 fns = api["functions"]87 for name, obj in fns.items():88 if not any(k in obj for k in possible_keys):89 err = f"error: function {name} does not contain at least one required property: {possible_keys}"90 errors.append(err)91 92 if errors:93 raise DocgenAPIFormatError("\n".join(errors))94 95 96def load_api(header: Header) -> Dict:97 api = header.docgen_yaml.read_text(encoding="utf-8")98 return yaml.safe_load(api)99 100 101def print_tbl_dir(name):102 print(103 f"""104.. list-table::105 :widths: auto106 :align: center107 :header-rows: 1108 109 * - {name}110 - Implemented111 - C23 Standard Section112 - POSIX Docs"""113 )114 115 116def print_functions_rst(header: Header, functions: Dict):117 tbl_hdr = "Functions"118 print(tbl_hdr)119 print("=" * len(tbl_hdr))120 121 print_tbl_dir("Function")122 123 for name in sorted(functions.keys()):124 print(f" * - {name}")125 126 if header.fns_dir_exists() and header.implements_fn(name):127 print(" - |check|")128 else:129 print(" -")130 131 if "c-definition" in functions[name]:132 print(f' - {functions[name]["c-definition"]}')133 else:134 print(" -")135 136 if "in-latest-posix" in functions[name]:137 print(138 f" - `POSIX.1-2024 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/{name}.html>`__"139 )140 elif "removed-in-posix-2008" in functions[name]:141 print(142 f" - `removed in POSIX.1-2008 <https://pubs.opengroup.org/onlinepubs/007904875/functions/{name}.html>`__"143 )144 elif "removed-in-posix-2024" in functions[name]:145 print(146 f" - `removed in POSIX.1-2024 <https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/functions/{name}.html>`__"147 )148 else:149 print(" -")150 151 152def print_macros_rst(header: Header, macros: Dict):153 tbl_hdr = "Macros"154 print(tbl_hdr)155 print("=" * len(tbl_hdr))156 157 print_tbl_dir("Macro")158 159 for name in sorted(macros.keys()):160 print(f" * - {name}")161 162 if header.macro_file_exists() and header.implements_macro(name):163 print(" - |check|")164 else:165 print(" -")166 167 if "c-definition" in macros[name]:168 print(f' - {macros[name]["c-definition"]}')169 else:170 print(" -")171 172 if "in-latest-posix" in macros[name]:173 print(174 f" - `POSIX.1-2024 <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/{header.name}.html>`__"175 )176 else:177 print(" -")178 print()179 180 181def print_impl_status_rst(header: Header, api: Dict):182 if os.sep in header.name:183 print(".. include:: ../../check.rst\n")184 else:185 print(".. include:: ../check.rst\n")186 187 print("=" * len(header.name))188 print(header.name)189 print("=" * len(header.name))190 print()191 192 # the macro and function sections are both optional193 if "macros" in api:194 print_macros_rst(header, api["macros"])195 196 if "functions" in api:197 print_functions_rst(header, api["functions"])198 199 200# This code implicitly relies on docgen.py being in the same dir as the yaml201# files and is likely to need to be fixed when re-integrating docgen into202# hdrgen.203def get_choices() -> list:204 choices = []205 for path in Path(__file__).parent.rglob("*.yaml"):206 fname = path.with_suffix(".h").name207 if path.parent != Path(__file__).parent:208 fname = path.parent.name + os.sep + fname209 choices.append(fname)210 return choices211 212 213def parse_args() -> Namespace:214 parser = ArgumentParser()215 parser.add_argument("header_name", choices=get_choices())216 return parser.parse_args()217 218 219if __name__ == "__main__":220 args = parse_args()221 header = Header(args.header_name)222 api = load_api(header)223 check_api(header, api)224 225 print_impl_status_rst(header, api)226