1#!/usr/bin/python3 -i 2# 3# Copyright 2013-2021 The Khronos Group Inc. 4# 5# SPDX-License-Identifier: Apache-2.0 6 7import os 8import re 9import sys 10from functools import total_ordering 11from generator import GeneratorOptions, OutputGenerator, regSortFeatures, write 12 13class ExtensionMetaDocGeneratorOptions(GeneratorOptions): 14 """ExtensionMetaDocGeneratorOptions - subclass of GeneratorOptions. 15 16 Represents options during extension metainformation generation for Asciidoc""" 17 def __init__(self, *args, **kwargs): 18 super().__init__(*args, **kwargs) 19 20 21EXT_NAME_DECOMPOSE_RE = re.compile(r'[A-Z]+_(?P<tag>[A-Z]+)_(?P<name>[\w_]+)') 22 23 24@total_ordering 25class Extension: 26 def __init__(self, 27 generator, # needed for logging and API conventions 28 filename, 29 name, 30 number, 31 ext_type, 32 requires, 33 requiresCore, 34 contact, 35 promotedTo, 36 deprecatedBy, 37 obsoletedBy, 38 provisional, 39 revision, 40 specialuse ): 41 self.generator = generator 42 self.conventions = generator.genOpts.conventions 43 self.filename = filename 44 self.name = name 45 self.number = number 46 self.ext_type = ext_type 47 self.requires = requires 48 self.requiresCore = requiresCore 49 self.contact = contact 50 self.promotedTo = promotedTo 51 self.deprecatedBy = deprecatedBy 52 self.obsoletedBy = obsoletedBy 53 self.provisional = provisional 54 self.revision = revision 55 self.specialuse = specialuse 56 57 self.deprecationType = None 58 self.supercedingAPIVersion = None 59 self.supercedingExtension = None 60 61 if self.promotedTo is not None and self.deprecatedBy is not None and self.obsoletedBy is not None: 62 self.generator.logMsg('warn', 'All \'promotedto\', \'deprecatedby\' and \'obsoletedby\' attributes used on extension ' + self.name + '! Ignoring \'promotedto\' and \'deprecatedby\'.') 63 elif self.promotedTo is not None and self.deprecatedBy is not None: 64 self.generator.logMsg('warn', 'Both \'promotedto\' and \'deprecatedby\' attributes used on extension ' + self.name + '! Ignoring \'deprecatedby\'.') 65 elif self.promotedTo is not None and self.obsoletedBy is not None: 66 self.generator.logMsg('warn', 'Both \'promotedto\' and \'obsoletedby\' attributes used on extension ' + self.name + '! Ignoring \'promotedto\'.') 67 elif self.deprecatedBy is not None and self.obsoletedBy is not None: 68 self.generator.logMsg('warn', 'Both \'deprecatedby\' and \'obsoletedby\' attributes used on extension ' + self.name + '! Ignoring \'deprecatedby\'.') 69 70 supercededBy = None 71 if self.promotedTo is not None: 72 self.deprecationType = 'promotion' 73 supercededBy = promotedTo 74 elif self.deprecatedBy is not None: 75 self.deprecationType = 'deprecation' 76 supercededBy = deprecatedBy 77 elif self.obsoletedBy is not None: 78 self.deprecationType = 'obsoletion' 79 supercededBy = obsoletedBy 80 81 if supercededBy is not None: 82 if supercededBy == '' and not self.deprecationType == 'promotion': 83 pass # supercedingAPIVersion, supercedingExtension is None 84 elif supercededBy.startswith(self.conventions.api_version_prefix): 85 self.supercedingAPIVersion = supercededBy 86 elif supercededBy.startswith(self.conventions.api_prefix): 87 self.supercedingExtension = supercededBy 88 else: 89 self.generator.logMsg('error', 'Unrecognized ' + self.deprecationType + ' attribute value \'' + supercededBy + '\'!') 90 91 match = EXT_NAME_DECOMPOSE_RE.match(self.name) 92 self.vendor = match.group('tag') 93 self.bare_name = match.group('name') 94 95 def __str__(self): 96 return self.name 97 def __eq__(self, other): 98 return self.name == other.name 99 def __ne__(self, other): 100 return self.name != other.name 101 102 def __lt__(self, other): 103 self_is_KHR = self.name.startswith(self.conventions.KHR_prefix) 104 self_is_EXT = self.name.startswith(self.conventions.EXT_prefix) 105 other_is_KHR = other.name.startswith(self.conventions.KHR_prefix) 106 other_is_EXT = other.name.startswith(self.conventions.EXT_prefix) 107 108 swap = False 109 if self_is_KHR and not other_is_KHR: 110 return not swap 111 if other_is_KHR and not self_is_KHR: 112 return swap 113 if self_is_EXT and not other_is_EXT: 114 return not swap 115 if other_is_EXT and not self_is_EXT: 116 return swap 117 118 return self.name < other.name 119 120 def typeToStr(self): 121 if self.ext_type == 'instance': 122 return 'Instance extension' 123 if self.ext_type == 'device': 124 return 'Device extension' 125 126 if self.ext_type is not None: 127 self.generator.logMsg('warn', 'The type attribute of ' + self.name + ' extension is neither \'instance\' nor \'device\'. That is invalid (at the time this script was written).') 128 else: # should be unreachable 129 self.generator.logMsg('error', 'Logic error in typeToStr(): Missing type attribute!') 130 return None 131 132 def specLink(self, xrefName, xrefText, isRefpage = False): 133 """Generate a string containing a link to a specification anchor in 134 asciidoctor markup form. 135 136 - xrefName - anchor name in the spec 137 - xrefText - text to show for the link, or None 138 - isRefpage = True if generating a refpage include, False if 139 generating a specification extension appendix include""" 140 141 if isRefpage: 142 # Always link into API spec 143 specURL = self.conventions.specURL('api') 144 return 'link:{}#{}[{}^]'.format(specURL, xrefName, xrefText) 145 else: 146 return '<<' + xrefName + ', ' + xrefText + '>>' 147 148 def conditionalLinkCoreAPI(self, apiVersion, linkSuffix, isRefpage): 149 versionMatch = re.match(self.conventions.api_version_prefix + r'(\d+)_(\d+)', apiVersion) 150 major = versionMatch.group(1) 151 minor = versionMatch.group(2) 152 153 dottedVersion = major + '.' + minor 154 155 xrefName = 'versions-' + dottedVersion + linkSuffix 156 xrefText = self.conventions.api_name() + ' ' + dottedVersion 157 158 doc = 'ifdef::' + apiVersion + '[]\n' 159 doc += ' ' + self.specLink(xrefName, xrefText, isRefpage) + '\n' 160 doc += 'endif::' + apiVersion + '[]\n' 161 doc += 'ifndef::' + apiVersion + '[]\n' 162 doc += ' ' + self.conventions.api_name() + ' ' + dottedVersion + '\n' 163 doc += 'endif::' + apiVersion + '[]\n' 164 165 return doc 166 167 def conditionalLinkExt(self, extName, indent = ' '): 168 doc = 'ifdef::' + extName + '[]\n' 169 doc += indent + self.conventions.formatExtension(extName) + '\n' 170 doc += 'endif::' + extName + '[]\n' 171 doc += 'ifndef::' + extName + '[]\n' 172 doc += indent + '`' + extName + '`\n' 173 doc += 'endif::' + extName + '[]\n' 174 175 return doc 176 177 def resolveDeprecationChain(self, extensionsList, succeededBy, isRefpage, file): 178 ext = next(x for x in extensionsList if x.name == succeededBy) 179 180 if ext.deprecationType: 181 if ext.deprecationType == 'promotion': 182 if ext.supercedingAPIVersion: 183 write(' ** Which in turn was _promoted_ to\n' + ext.conditionalLinkCoreAPI(ext.supercedingAPIVersion, '-promotions', isRefpage), file=file) 184 else: # ext.supercedingExtension 185 write(' ** Which in turn was _promoted_ to extension\n' + ext.conditionalLinkExt(ext.supercedingExtension), file=file) 186 ext.resolveDeprecationChain(extensionsList, ext.supercedingExtension, file) 187 elif ext.deprecationType == 'deprecation': 188 if ext.supercedingAPIVersion: 189 write(' ** Which in turn was _deprecated_ by\n' + ext.conditionalLinkCoreAPI(ext.supercedingAPIVersion, '-new-feature', isRefpage), file=file) 190 elif ext.supercedingExtension: 191 write(' ** Which in turn was _deprecated_ by\n' + ext.conditionalLinkExt(ext.supercedingExtension) + ' extension', file=file) 192 ext.resolveDeprecationChain(extensionsList, ext.supercedingExtension, file) 193 else: 194 write(' ** Which in turn was _deprecated_ without replacement', file=file) 195 elif ext.deprecationType == 'obsoletion': 196 if ext.supercedingAPIVersion: 197 write(' ** Which in turn was _obsoleted_ by\n' + ext.conditionalLinkCoreAPI(ext.supercedingAPIVersion, '-new-feature', isRefpage), file=file) 198 elif ext.supercedingExtension: 199 write(' ** Which in turn was _obsoleted_ by\n' + ext.conditionalLinkExt(ext.supercedingExtension) + ' extension', file=file) 200 ext.resolveDeprecationChain(extensionsList, ext.supercedingExtension, file) 201 else: 202 write(' ** Which in turn was _obsoleted_ without replacement', file=file) 203 else: # should be unreachable 204 self.generator.logMsg('error', 'Logic error in resolveDeprecationChain(): deprecationType is neither \'promotion\', \'deprecation\' nor \'obsoletion\'!') 205 206 207 def writeTag(self, tag, value, isRefpage, fp): 208 """Write a tag and (if non-None) a tag value to a file. 209 210 - tag - string tag name 211 - value - tag value, or None 212 - isRefpage - controls style in which the tag is marked up 213 - fp - open file pointer to write to""" 214 215 if isRefpage: 216 # Use subsection headers for the tag name 217 tagPrefix = '== ' 218 tagSuffix = '' 219 else: 220 # Use an bolded item list for the tag name 221 tagPrefix = '*' 222 tagSuffix = '*::' 223 224 write(tagPrefix + tag + tagSuffix, file=fp) 225 if value is not None: 226 write(value, file=fp) 227 228 if isRefpage: 229 write('', file=fp) 230 231 def makeMetafile(self, extensionsList, isRefpage = False): 232 """Generate a file containing extension metainformation in 233 asciidoctor markup form. 234 235 - extensionsList - list of extensions spec is being generated against 236 - isRefpage - True if generating a refpage include, False if 237 generating a specification extension appendix include""" 238 239 if isRefpage: 240 filename = self.filename.replace('meta/', 'meta/refpage.') 241 else: 242 filename = self.filename 243 244 fp = self.generator.newFile(filename) 245 246 if not isRefpage: 247 write('[[' + self.name + ']]', file=fp) 248 write('=== ' + self.name, file=fp) 249 write('', file=fp) 250 251 self.writeTag('Name String', '`' + self.name + '`', isRefpage, fp) 252 self.writeTag('Extension Type', self.typeToStr(), isRefpage, fp) 253 254 self.writeTag('Registered Extension Number', self.number, isRefpage, fp) 255 self.writeTag('Revision', self.revision, isRefpage, fp) 256 257 # Only API extension dependencies are coded in XML, others are explicit 258 self.writeTag('Extension and Version Dependencies', None, isRefpage, fp) 259 260 write(' * Requires ' + self.conventions.api_name() + ' ' + self.requiresCore, file=fp) 261 if self.requires: 262 for dep in self.requires.split(','): 263 write(' * Requires', self.conventions.formatExtension(dep), 264 file=fp) 265 if self.provisional == 'true': 266 write(' * *This is a _provisional_ extension and must: be used with caution.', file=fp) 267 write(' See the ' + 268 self.specLink(xrefName = 'boilerplate-provisional-header', 269 xrefText = 'description', 270 isRefpage = isRefpage) + 271 ' of provisional header files for enablement and stability details.*', file=fp) 272 write('', file=fp) 273 274 if self.deprecationType: 275 self.writeTag('Deprecation state', None, isRefpage, fp) 276 277 if self.deprecationType == 'promotion': 278 if self.supercedingAPIVersion: 279 write(' * _Promoted_ to\n' + self.conditionalLinkCoreAPI(self.supercedingAPIVersion, '-promotions', isRefpage), file=fp) 280 else: # ext.supercedingExtension 281 write(' * _Promoted_ to\n' + self.conditionalLinkExt(self.supercedingExtension) + ' extension', file=fp) 282 self.resolveDeprecationChain(extensionsList, self.supercedingExtension, isRefpage, fp) 283 elif self.deprecationType == 'deprecation': 284 if self.supercedingAPIVersion: 285 write(' * _Deprecated_ by\n' + self.conditionalLinkCoreAPI(self.supercedingAPIVersion, '-new-features', isRefpage), file=fp) 286 elif self.supercedingExtension: 287 write(' * _Deprecated_ by\n' + self.conditionalLinkExt(self.supercedingExtension) + ' extension' , file=fp) 288 self.resolveDeprecationChain(extensionsList, self.supercedingExtension, isRefpage, fp) 289 else: 290 write(' * _Deprecated_ without replacement' , file=fp) 291 elif self.deprecationType == 'obsoletion': 292 if self.supercedingAPIVersion: 293 write(' * _Obsoleted_ by\n' + self.conditionalLinkCoreAPI(self.supercedingAPIVersion, '-new-features', isRefpage), file=fp) 294 elif self.supercedingExtension: 295 write(' * _Obsoleted_ by\n' + self.conditionalLinkExt(self.supercedingExtension) + ' extension' , file=fp) 296 self.resolveDeprecationChain(extensionsList, self.supercedingExtension, isRefpage, fp) 297 else: 298 # TODO: Does not make sense to retroactively ban use of extensions from 1.0. 299 # Needs some tweaks to the semantics and this message, when such extension(s) occur. 300 write(' * _Obsoleted_ without replacement' , file=fp) 301 else: # should be unreachable 302 self.generator.logMsg('error', 'Logic error in makeMetafile(): deprecationType is neither \'promotion\', \'deprecation\' nor \'obsoletion\'!') 303 write('', file=fp) 304 305 if self.specialuse is not None: 306 specialuses = self.specialuse.split(',') 307 if len(specialuses) > 1: 308 header = 'Special Uses' 309 else: 310 header = 'Special Use' 311 self.writeTag(header, None, isRefpage, fp) 312 313 for use in specialuses: 314 # Each specialuse attribute value expands an asciidoctor 315 # attribute of the same name, instead of using the shorter, 316 # and harder to understand attribute 317 write('* {}'.format( 318 self.specLink( 319 xrefName = self.conventions.special_use_section_anchor, 320 xrefText = '{' + use + '}', 321 isRefpage = isRefpage)), file=fp) 322 write('', file=fp) 323 324 if self.conventions.write_contacts: 325 self.writeTag('Contact', None, isRefpage, fp) 326 327 contacts = self.contact.split(',') 328 for contact in contacts: 329 contactWords = contact.strip().split() 330 name = ' '.join(contactWords[:-1]) 331 handle = contactWords[-1] 332 if handle.startswith('gitlab:'): 333 prettyHandle = 'icon:gitlab[alt=GitLab, role="red"]' + handle.replace('gitlab:@', '') 334 elif handle.startswith('@'): 335 issuePlaceholderText = '[' + self.name + '] ' + handle 336 issuePlaceholderText += '%0A<<Here describe the issue or question you have about the ' + self.name + ' extension>>' 337 trackerLink = 'link:++https://github.com/KhronosGroup/Vulkan-Docs/issues/new?body=' + issuePlaceholderText + '++' 338 prettyHandle = trackerLink + '[icon:github[alt=GitHub,role="black"]' + handle[1:] + ',window=_blank,opts=nofollow]' 339 else: 340 prettyHandle = handle 341 342 write(' * ' + name + ' ' + prettyHandle, file=fp) 343 write('', file=fp) 344 345 # Check if a proposal document for this extension exists in the 346 # current repository, and link to the same document (parameterized 347 # by a URL prefix attribute) if it does. 348 # The assumption is that a proposal document for an extension 349 # VK_name will be located in 'proposals/VK_name.asciidoc' relative 350 # to the repository root, and that this script will be invoked from 351 # the repository root. 352 path = 'proposals/{}.asciidoc'.format(self.name) 353 if os.path.exists(path) and os.access(path, os.R_OK): 354 self.writeTag('Extension Proposal', 355 'link:{{specRepositoryURL}}/{}[{}]'.format(path, self.name), isRefpage, fp) 356 357 # If this is metadata to be included in a refpage, adjust the 358 # leveloffset to account for the relative structure of the extension 359 # appendices vs. refpages. 360 if isRefpage: 361 write(':leveloffset: -1', file=fp) 362 363 fp.close() 364 365class ExtensionMetaDocOutputGenerator(OutputGenerator): 366 """ExtensionMetaDocOutputGenerator - subclass of OutputGenerator. 367 368 Generates AsciiDoc includes with metainformation for the API extension 369 appendices. The fields used from <extension> tags in the API XML are: 370 371 - name extension name string 372 - number extension number (optional) 373 - contact name and GitHub login or email address (optional) 374 - type 'instance' | 'device' (optional) 375 - requires list of comma-separated required API extensions (optional) 376 - requiresCore required core version of API (optional) 377 - promotedTo extension or API version it was promoted to 378 - deprecatedBy extension or API version which deprecated this extension, 379 or empty string if deprecated without replacement 380 - obsoletedBy extension or API version which obsoleted this extension, 381 or empty string if obsoleted without replacement 382 - provisional 'true' if this extension is released provisionally""" 383 384 def __init__(self, *args, **kwargs): 385 super().__init__(*args, **kwargs) 386 self.extensions = [] 387 # List of strings containing all vendor tags 388 self.vendor_tags = [] 389 self.file_suffix = '' 390 391 def newFile(self, filename): 392 self.logMsg('diag', '# Generating include file:', filename) 393 fp = open(filename, 'w', encoding='utf-8') 394 write(self.genOpts.conventions.warning_comment, file=fp) 395 return fp 396 397 def beginFile(self, genOpts): 398 OutputGenerator.beginFile(self, genOpts) 399 400 self.directory = self.genOpts.directory 401 self.file_suffix = self.genOpts.conventions.file_suffix 402 403 # Iterate over all 'tag' Elements and add the names of all the valid vendor 404 # tags to the list 405 root = self.registry.tree.getroot() 406 for tag in root.findall('tags/tag'): 407 self.vendor_tags.append(tag.get('name')) 408 409 # Create subdirectory, if needed 410 self.makeDir(self.directory) 411 412 def conditionalExt(self, extName, content, ifdef = None, condition = None): 413 doc = '' 414 415 innerdoc = 'ifdef::' + extName + '[]\n' 416 innerdoc += content + '\n' 417 innerdoc += 'endif::' + extName + '[]\n' 418 419 if ifdef: 420 if ifdef == 'ifndef': 421 if condition: 422 doc += 'ifndef::' + condition + '[]\n' 423 doc += innerdoc 424 doc += 'endif::' + condition + '[]\n' 425 else: # no condition is as if condition is defined; "nothing" is always defined :p 426 pass # so no output 427 elif ifdef == 'ifdef': 428 if condition: 429 doc += 'ifdef::' + condition + '+' + extName + '[]\n' 430 doc += content + '\n' # does not include innerdoc; the ifdef was merged with the one above 431 doc += 'endif::' + condition + '+' + extName + '[]\n' 432 else: # no condition is as if condition is defined; "nothing" is always defined :p 433 doc += innerdoc 434 else: # should be unreachable 435 raise RuntimeError('Should be unreachable: ifdef is neither \'ifdef \' nor \'ifndef\'!') 436 else: 437 doc += innerdoc 438 439 return doc 440 441 def makeExtensionInclude(self, ext): 442 return self.conventions.extension_include_string(ext) 443 444 def endFile(self): 445 self.extensions.sort() 446 447 # Generate metadoc extension files, in refpage and non-refpage form 448 for ext in self.extensions: 449 ext.makeMetafile(self.extensions, isRefpage = False) 450 if self.conventions.write_refpage_include: 451 ext.makeMetafile(self.extensions, isRefpage = True) 452 453 # Generate list of promoted extensions 454 promotedExtensions = {} 455 for ext in self.extensions: 456 if ext.deprecationType == 'promotion' and ext.supercedingAPIVersion: 457 promotedExtensions.setdefault(ext.supercedingAPIVersion, []).append(ext) 458 459 for coreVersion, extensions in promotedExtensions.items(): 460 promoted_extensions_fp = self.newFile(self.directory + '/promoted_extensions_' + coreVersion + self.file_suffix) 461 462 for ext in extensions: 463 indent = '' 464 write(' * {blank}\n+\n' + ext.conditionalLinkExt(ext.name, indent), file=promoted_extensions_fp) 465 466 promoted_extensions_fp.close() 467 468 # Re-sort to match earlier behavior 469 # TODO: Remove this extra sort when re-arranging section order OK. 470 471 def makeSortKey(ext): 472 name = ext.name.lower() 473 prefixes = self.conventions.extension_index_prefixes 474 for i, prefix in enumerate(prefixes): 475 if ext.name.startswith(prefix): 476 return (i, name) 477 return (len(prefixes), name) 478 479 self.extensions.sort(key=makeSortKey) 480 481 # Generate include directives for the extensions appendix, grouping 482 # extensions by status (current, deprecated, provisional, etc.) 483 with self.newFile(self.directory + '/current_extensions_appendix' + self.file_suffix) as current_extensions_appendix_fp, \ 484 self.newFile(self.directory + '/deprecated_extensions_appendix' + self.file_suffix) as deprecated_extensions_appendix_fp, \ 485 self.newFile(self.directory + '/current_extension_appendices' + self.file_suffix) as current_extension_appendices_fp, \ 486 self.newFile(self.directory + '/current_extension_appendices_toc' + self.file_suffix) as current_extension_appendices_toc_fp, \ 487 self.newFile(self.directory + '/deprecated_extension_appendices' + self.file_suffix) as deprecated_extension_appendices_fp, \ 488 self.newFile(self.directory + '/deprecated_extension_appendices_toc' + self.file_suffix) as deprecated_extension_appendices_toc_fp, \ 489 self.newFile(self.directory + '/deprecated_extensions_guard_macro' + self.file_suffix) as deprecated_extensions_guard_macro_fp, \ 490 self.newFile(self.directory + '/provisional_extensions_appendix' + self.file_suffix) as provisional_extensions_appendix_fp, \ 491 self.newFile(self.directory + '/provisional_extension_appendices' + self.file_suffix) as provisional_extension_appendices_fp, \ 492 self.newFile(self.directory + '/provisional_extension_appendices_toc' + self.file_suffix) as provisional_extension_appendices_toc_fp, \ 493 self.newFile(self.directory + '/provisional_extensions_guard_macro' + self.file_suffix) as provisional_extensions_guard_macro_fp: 494 495 # Note: there is a hardwired assumption in creating the 496 # include:: directives below that all of these files are located 497 # in the 'meta/' subdirectory of the generated files directory. 498 # This is difficult to change, and it is very unlikely changing 499 # it will be needed. 500 501 write('', file=current_extensions_appendix_fp) 502 write('include::{generated}/meta/deprecated_extensions_guard_macro' + self.file_suffix + '[]', file=current_extensions_appendix_fp) 503 write('', file=current_extensions_appendix_fp) 504 write('ifndef::HAS_DEPRECATED_EXTENSIONS[]', file=current_extensions_appendix_fp) 505 write('[[extension-appendices-list]]', file=current_extensions_appendix_fp) 506 write('== List of Extensions', file=current_extensions_appendix_fp) 507 write('endif::HAS_DEPRECATED_EXTENSIONS[]', file=current_extensions_appendix_fp) 508 write('ifdef::HAS_DEPRECATED_EXTENSIONS[]', file=current_extensions_appendix_fp) 509 write('[[extension-appendices-list]]', file=current_extensions_appendix_fp) 510 write('== List of Current Extensions', file=current_extensions_appendix_fp) 511 write('endif::HAS_DEPRECATED_EXTENSIONS[]', file=current_extensions_appendix_fp) 512 write('', file=current_extensions_appendix_fp) 513 write('include::{generated}/meta/current_extension_appendices_toc' + self.file_suffix + '[]', file=current_extensions_appendix_fp) 514 write('\n<<<\n', file=current_extensions_appendix_fp) 515 write('include::{generated}/meta/current_extension_appendices' + self.file_suffix + '[]', file=current_extensions_appendix_fp) 516 517 write('', file=deprecated_extensions_appendix_fp) 518 write('include::{generated}/meta/deprecated_extensions_guard_macro' + self.file_suffix + '[]', file=deprecated_extensions_appendix_fp) 519 write('', file=deprecated_extensions_appendix_fp) 520 write('ifdef::HAS_DEPRECATED_EXTENSIONS[]', file=deprecated_extensions_appendix_fp) 521 write('[[deprecated-extension-appendices-list]]', file=deprecated_extensions_appendix_fp) 522 write('== List of Deprecated Extensions', file=deprecated_extensions_appendix_fp) 523 write('include::{generated}/meta/deprecated_extension_appendices_toc' + self.file_suffix + '[]', file=deprecated_extensions_appendix_fp) 524 write('\n<<<\n', file=deprecated_extensions_appendix_fp) 525 write('include::{generated}/meta/deprecated_extension_appendices' + self.file_suffix + '[]', file=deprecated_extensions_appendix_fp) 526 write('endif::HAS_DEPRECATED_EXTENSIONS[]', file=deprecated_extensions_appendix_fp) 527 528 # add include guards to allow multiple includes 529 write('ifndef::DEPRECATED_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD[]', file=deprecated_extensions_guard_macro_fp) 530 write(':DEPRECATED_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD:\n', file=deprecated_extensions_guard_macro_fp) 531 write('ifndef::PROVISIONAL_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD[]', file=provisional_extensions_guard_macro_fp) 532 write(':PROVISIONAL_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD:\n', file=provisional_extensions_guard_macro_fp) 533 534 write('', file=provisional_extensions_appendix_fp) 535 write('include::{generated}/meta/provisional_extensions_guard_macro' + self.file_suffix + '[]', file=provisional_extensions_appendix_fp) 536 write('', file=provisional_extensions_appendix_fp) 537 write('ifdef::HAS_PROVISIONAL_EXTENSIONS[]', file=provisional_extensions_appendix_fp) 538 write('[[provisional-extension-appendices-list]]', file=provisional_extensions_appendix_fp) 539 write('== List of Provisional Extensions', file=provisional_extensions_appendix_fp) 540 write('include::{generated}/meta/provisional_extension_appendices_toc' + self.file_suffix + '[]', file=provisional_extensions_appendix_fp) 541 write('\n<<<\n', file=provisional_extensions_appendix_fp) 542 write('include::{generated}/meta/provisional_extension_appendices' + self.file_suffix + '[]', file=provisional_extensions_appendix_fp) 543 write('endif::HAS_PROVISIONAL_EXTENSIONS[]', file=provisional_extensions_appendix_fp) 544 545 for ext in self.extensions: 546 include = self.makeExtensionInclude(ext) 547 link = ' * ' + self.conventions.formatExtension(ext.name) 548 if ext.provisional == 'true': 549 write(self.conditionalExt(ext.name, include), file=provisional_extension_appendices_fp) 550 write(self.conditionalExt(ext.name, link), file=provisional_extension_appendices_toc_fp) 551 write(self.conditionalExt(ext.name, ':HAS_PROVISIONAL_EXTENSIONS:'), file=provisional_extensions_guard_macro_fp) 552 elif ext.deprecationType is None: 553 write(self.conditionalExt(ext.name, include), file=current_extension_appendices_fp) 554 write(self.conditionalExt(ext.name, link), file=current_extension_appendices_toc_fp) 555 else: 556 condition = ext.supercedingAPIVersion if ext.supercedingAPIVersion else ext.supercedingExtension # potentially None too 557 558 write(self.conditionalExt(ext.name, include, 'ifndef', condition), file=current_extension_appendices_fp) 559 write(self.conditionalExt(ext.name, link, 'ifndef', condition), file=current_extension_appendices_toc_fp) 560 561 write(self.conditionalExt(ext.name, include, 'ifdef', condition), file=deprecated_extension_appendices_fp) 562 write(self.conditionalExt(ext.name, link, 'ifdef', condition), file=deprecated_extension_appendices_toc_fp) 563 564 write(self.conditionalExt(ext.name, ':HAS_DEPRECATED_EXTENSIONS:', 'ifdef', condition), file=deprecated_extensions_guard_macro_fp) 565 566 write('endif::DEPRECATED_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD[]', file=deprecated_extensions_guard_macro_fp) 567 write('endif::PROVISIONAL_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD[]', file=provisional_extensions_guard_macro_fp) 568 569 OutputGenerator.endFile(self) 570 571 def beginFeature(self, interface, emit): 572 # Start processing in superclass 573 OutputGenerator.beginFeature(self, interface, emit) 574 575 if interface.tag != 'extension': 576 self.logMsg('diag', 'beginFeature: ignoring non-extension feature', self.featureName) 577 return 578 579 # These attributes must exist 580 name = self.featureName 581 number = self.getAttrib(interface, 'number') 582 ext_type = self.getAttrib(interface, 'type') 583 revision = self.getSpecVersion(interface, name) 584 585 # These attributes are optional 586 OPTIONAL = False 587 requires = self.getAttrib(interface, 'requires', OPTIONAL) 588 requiresCore = self.getAttrib(interface, 'requiresCore', OPTIONAL, '1.0') # TODO update this line with update_version.py 589 contact = self.getAttrib(interface, 'contact', OPTIONAL) 590 promotedTo = self.getAttrib(interface, 'promotedto', OPTIONAL) 591 deprecatedBy = self.getAttrib(interface, 'deprecatedby', OPTIONAL) 592 obsoletedBy = self.getAttrib(interface, 'obsoletedby', OPTIONAL) 593 provisional = self.getAttrib(interface, 'provisional', OPTIONAL, 'false') 594 specialuse = self.getAttrib(interface, 'specialuse', OPTIONAL) 595 596 filename = self.directory + '/' + name + self.file_suffix 597 598 extdata = Extension( 599 generator = self, 600 filename = filename, 601 name = name, 602 number = number, 603 ext_type = ext_type, 604 requires = requires, 605 requiresCore = requiresCore, 606 contact = contact, 607 promotedTo = promotedTo, 608 deprecatedBy = deprecatedBy, 609 obsoletedBy = obsoletedBy, 610 provisional = provisional, 611 revision = revision, 612 specialuse = specialuse) 613 self.extensions.append(extdata) 614 615 616 def endFeature(self): 617 # Finish processing in superclass 618 OutputGenerator.endFeature(self) 619 620 def getAttrib(self, elem, attribute, required=True, default=None): 621 """Query an attribute from an element, or return a default value 622 623 - elem - element to query 624 - attribute - attribute name 625 - required - whether attribute must exist 626 - default - default value if attribute not present""" 627 attrib = elem.get(attribute, default) 628 if required and (attrib is None): 629 name = elem.get('name', 'UNKNOWN') 630 self.logMsg('error', 'While processing \'' + self.featureName + ', <' + elem.tag + '> \'' + name + '\' does not contain required attribute \'' + attribute + '\'') 631 return attrib 632 633 def numbersToWords(self, name): 634 allowlist = ['WIN32', 'INT16', 'D3D1'] 635 636 # temporarily replace allowlist items 637 for i, w in enumerate(allowlist): 638 name = re.sub(w, '{' + str(i) + '}', name) 639 640 name = re.sub(r'(?<=[A-Z])(\d+)(?![A-Z])', r'_\g<1>', name) 641 642 # undo allowlist substitution 643 for i, w in enumerate(allowlist): 644 name = re.sub('\\{' + str(i) + '}', w, name) 645 646 return name 647 648 def getSpecVersion(self, elem, extname, default=None): 649 """Determine the extension revision from the EXTENSION_NAME_SPEC_VERSION 650 enumerant. 651 652 - elem - <extension> element to query 653 - extname - extension name from the <extension> 'name' attribute 654 - default - default value if SPEC_VERSION token not present""" 655 # The literal enumerant name to match 656 versioningEnumName = self.numbersToWords(extname.upper()) + '_SPEC_VERSION' 657 658 for enum in elem.findall('./require/enum'): 659 enumName = self.getAttrib(enum, 'name') 660 if enumName == versioningEnumName: 661 return self.getAttrib(enum, 'value') 662 663 #if not found: 664 for enum in elem.findall('./require/enum'): 665 enumName = self.getAttrib(enum, 'name') 666 if enumName.find('SPEC_VERSION') != -1: 667 self.logMsg('diag', 'Missing ' + versioningEnumName + '! Potential misnamed candidate ' + enumName + '.') 668 return self.getAttrib(enum, 'value') 669 670 self.logMsg('error', 'Missing ' + versioningEnumName + '!') 671 return default 672