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 sys 14import json 15 16from header import Header 17 18 19class DocgenAPIFormatError(Exception): 20 """Raised on fatal formatting errors with a description of a formatting error""" 21 22 23def check_api(header: Header, api: Dict): 24 """ 25 Checks that docgen json files are properly formatted. If there are any 26 fatal formatting errors, raises exceptions with error messages useful for 27 fixing formatting. Warnings are printed to stderr on non-fatal formatting 28 errors. The code that runs after ``check_api(api)`` is called expects that 29 ``check_api`` executed without raising formatting exceptions so the json 30 matches the formatting specified here. 31 32 The json file may contain: 33 * an optional macros object 34 * an optional functions object 35 36 Formatting of ``macros`` and ``functions`` objects 37 ================================================== 38 39 If a macros or functions object is present, then it may contain nested 40 objects. Each of these nested objects should have a name matching a macro 41 or function's name, and each nested object must have the property: 42 ``"c-definition"`` or ``"posix-definition"``. 43 44 Description of properties 45 ========================= 46 The defined property is intended to be a reference to a part of the 47 standard that defines the function or macro. For the ``"c-definition"`` property, 48 this should be a C standard section number. For the ``"posix-definition"`` property, 49 this should be a link to the definition. 50 51 :param api: docgen json file contents parsed into a dict 52 """ 53 errors = [] 54 cdef = "c-definition" 55 pdef = "posix-definition" 56 57 # Validate macros 58 if "macros" in api: 59 if not header.macro_file_exists(): 60 print( 61 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.", 62 file=sys.stderr, 63 ) 64 65 macros = api["macros"] 66 67 for name, obj in macros.items(): 68 if not (cdef in obj or pdef in obj): 69 err = f'error: Macro {name} does not contain at least one required property: "{cdef}" or "{pdef}"' 70 errors.append(err) 71 72 # Validate functions 73 if "functions" in api: 74 if not header.fns_dir_exists(): 75 print( 76 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.", 77 file=sys.stderr, 78 ) 79 80 fns = api["functions"] 81 for name, obj in fns.items(): 82 if not (cdef in obj or pdef in obj): 83 err = f'error: function {name} does not contain at least one required property: "{cdef}" or "{pdef}"' 84 errors.append(err) 85 86 if errors: 87 raise DocgenAPIFormatError("\n".join(errors)) 88 89 90def load_api(header: Header) -> Dict: 91 api = header.docgen_json.read_text(encoding="utf-8") 92 return json.loads(api) 93 94 95def print_tbl_dir(): 96 print( 97 f""" 98.. list-table:: 99 :widths: auto 100 :align: center 101 :header-rows: 1 102 103 * - Function 104 - Implemented 105 - C23 Standard Section 106 - POSIX.1-2017 Standard Section""" 107 ) 108 109 110def print_functions_rst(header: Header, functions: Dict): 111 tbl_hdr = "Functions" 112 print(tbl_hdr) 113 print("=" * len(tbl_hdr)) 114 115 print_tbl_dir() 116 117 for name in sorted(functions.keys()): 118 print(f" * - {name}") 119 120 if header.fns_dir_exists() and header.implements_fn(name): 121 print(" - |check|") 122 else: 123 print(" -") 124 125 if "c-definition" in functions[name]: 126 print(f' - {functions[name]["c-definition"]}') 127 else: 128 print(" -") 129 130 if "posix-definition" in functions[name]: 131 print(f' - {functions[name]["posix-definition"]}') 132 else: 133 print(" -") 134 135 136def print_macros_rst(header: Header, macros: Dict): 137 tbl_hdr = "Macros" 138 print(tbl_hdr) 139 print("=" * len(tbl_hdr)) 140 141 print_tbl_dir() 142 143 for name in sorted(macros.keys()): 144 print(f" * - {name}") 145 146 if header.macro_file_exists() and header.implements_macro(name): 147 print(" - |check|") 148 else: 149 print(" -") 150 151 if "c-definition" in macros[name]: 152 print(f' - {macros[name]["c-definition"]}') 153 else: 154 print(" -") 155 156 if "posix-definition" in macros[name]: 157 print(f' - {macros[name]["posix-definition"]}') 158 else: 159 print(" -") 160 print() 161 162 163def print_impl_status_rst(header: Header, api: Dict): 164 print(".. include:: check.rst\n") 165 166 print("=" * len(header.name)) 167 print(header.name) 168 print("=" * len(header.name)) 169 print() 170 171 # the macro and function sections are both optional 172 if "macros" in api: 173 print_macros_rst(header, api["macros"]) 174 175 if "functions" in api: 176 print_functions_rst(header, api["functions"]) 177 178 179def parse_args() -> Namespace: 180 parser = ArgumentParser() 181 choices = [p.with_suffix(".h").name for p in Path(__file__).parent.glob("*.json")] 182 parser.add_argument("header_name", choices=choices) 183 return parser.parse_args() 184 185 186if __name__ == "__main__": 187 args = parse_args() 188 header = Header(args.header_name) 189 api = load_api(header) 190 check_api(header, api) 191 192 print_impl_status_rst(header, api) 193